diff options
Diffstat (limited to '')
1294 files changed, 92140 insertions, 85186 deletions
diff --git a/.gitignore b/.gitignore index a876b705e..a20d3c54b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.la *.lo *.o +*.exe .deps .dirstamp Makefile @@ -31,7 +32,6 @@ ltmain.sh missing mkinstalldirs mpd -mpd.exe mpd.service stamp-h1 tags @@ -70,6 +70,7 @@ test/dump_rva2 test/dump_text_file test/test_byte_reverse test/test_vorbis_encoder +test/DumpDatabase /*.tar.gz /*.tar.bz2 @@ -37,9 +37,6 @@ Linux. You will need libasound. FIFO This is a mostly undocumented, developer plugin to transmit raw data. -MVP - http://en.wikipedia.org/wiki/Hauppauge_MediaMVP -A network media player. - OSS - http://www.opensound.com Open Sound System. @@ -59,9 +56,6 @@ You also need an encoder: either libvorbisenc (ogg), or liblame (mp3). OpenAL - http://kcat.strangesoft.net/openal.html Open Audio Library -libffado - http://www.ffado.org/ -For FireWire audio devices. - Optional Input Dependencies --------------------------- @@ -81,14 +75,17 @@ Alternative for MP3 support. Ogg Vorbis - http://www.xiph.org/ogg/vorbis/ For Ogg Vorbis support. You will need libogg and libvorbis. +libopus - http://www.opus-codec.org/ +Opus codec support + FLAC - http://flac.sourceforge.net/ -For FLAC support. You will need version 1.1.0 or higher of libflac. +For FLAC support. You will need version 1.2 or higher of libFLAC. Audio File - http://www.68k.org/~michael/audiofile/ For WAVE, AIFF, and AU support. You will need libaudiofile. FAAD2 - http://www.audiocoding.com/ -For MP4/AAC support. You will need libmp4ff. +For MP4/AAC support. libmpcdec - http://www.musepack.net/ For Musepack support. @@ -114,6 +111,9 @@ WAVE, AIFF, and many others. libwavpack - http://www.wavpack.com/ For WavPack playback. +libadplug - http://adplug.sourceforge.net/ +For AdLib playback. + despotify - https://github.com/SimonKagstrom/despotify For Spotify playback. @@ -196,9 +196,9 @@ Run $ mpd <config file> -First default is ~/.mpdconf then ~/.mpd/mpd.conf then /etc/mpd.conf. If -neither of these exist a mpd configuration file must be specified at -runtime. +First default is $XDG_CONFIG_HOME/mpd/mpd.conf then ~/.mpdconf then +~/.mpd/mpd.conf then /etc/mpd.conf. If neither of these exist a mpd +configuration file must be specified at runtime. A sample config file is included with the source of MPD, mpdconf.example. diff --git a/Makefile.am b/Makefile.am index 90fa3481c..eca519b01 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,9 +9,14 @@ bin_PROGRAMS = src/mpd noinst_LIBRARIES = \ libutil.a \ + libsystem.a \ + libevent.a \ libpcm.a \ + libconf.a \ libtag.a \ libinput.a \ + libfs.a \ + libdb_plugins.a \ libplaylist_plugins.a \ libdecoder_plugins.a \ libfilter_plugins.a \ @@ -19,10 +24,12 @@ noinst_LIBRARIES = \ liboutput_plugins.a src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ + $(LIBMPDCLIENT_CFLAGS) \ $(AVAHI_CFLAGS) \ $(LIBWRAP_CFLAGS) \ $(SQLITE_CFLAGS) src_mpd_LDADD = \ + $(DB_LIBS) \ $(PLAYLIST_LIBS) \ $(AVAHI_LIBS) \ $(LIBWRAP_LDFLAGS) \ @@ -35,325 +42,174 @@ src_mpd_LDADD = \ $(FILTER_LIBS) \ $(ENCODER_LIBS) \ $(MIXER_LIBS) \ + libconf.a \ + libevent.a \ + libsystem.a \ libutil.a \ + libfs.a \ $(SYSTEMD_DAEMON_LIBS) \ $(GLIB_LIBS) mpd_headers = \ src/check.h \ - src/notify.h \ - src/ack.h \ - src/ape.h \ - src/audio_format.h \ - src/audio_check.h \ - src/audio_parser.h \ - src/output_internal.h \ - src/output_api.h \ - src/output_list.h \ - src/output_all.h \ - src/output_thread.h \ - src/output_control.h \ - src/output_state.h \ - src/output_print.h \ - src/output_command.h \ - src/filter_internal.h \ - src/filter_config.h \ - src/filter_plugin.h \ - src/filter_registry.h \ - src/filter/autoconvert_filter_plugin.h \ - src/filter/chain_filter_plugin.h \ - src/filter/convert_filter_plugin.h \ - src/filter/replay_gain_filter_plugin.h \ - src/filter/volume_filter_plugin.h \ src/command.h \ - src/idle.h \ - src/cmdline.h \ - src/conf.h \ - src/crossfade.h \ - src/dbUtils.h \ - src/decoder_thread.h \ - src/decoder_control.h \ - src/decoder_plugin.h \ - src/decoder_command.h \ - src/decoder_buffer.h \ - src/decoder_api.h \ - src/decoder_plugin.h \ - src/decoder_internal.h \ - src/directory.h \ - src/directory_save.h \ - src/database.h \ - src/encoder_plugin.h \ - src/encoder_list.h \ - src/encoder_api.h \ - src/exclude.h \ - src/fd_util.h \ + src/gerror.h \ src/glib_compat.h \ - src/update.h \ - src/inotify_source.h \ - src/inotify_queue.h \ - src/inotify_update.h \ src/gcc.h \ - src/decoder_list.h \ - src/decoder_print.h \ - src/decoder/flac_compat.h \ - src/decoder/flac_metadata.h \ - src/decoder/flac_pcm.h \ - src/decoder/_flac_common.h \ - src/decoder/_ogg_common.h \ - src/decoder/pcm_decoder_plugin.h \ - src/input_init.h \ - src/input_plugin.h \ - src/input_registry.h \ - src/input_stream.h \ - src/input/file_input_plugin.h \ - src/input/ffmpeg_input_plugin.h \ - src/input/curl_input_plugin.h \ - src/input/rewind_input_plugin.h \ - src/input/mms_input_plugin.h \ - src/input/despotify_input_plugin.h \ - src/input/cdio_paranoia_input_plugin.h \ - src/despotify_utils.h \ - src/text_file.h \ - src/text_input_stream.h \ - src/icy_server.h \ - src/icy_metadata.h \ - src/client.h \ - src/client_internal.h \ - src/server_socket.h \ - src/listen.h \ - src/log.h \ - src/ls.h \ - src/main.h \ - src/mixer_all.h \ - src/mixer_api.h \ - src/mixer_control.h \ - src/mixer_list.h \ - src/event_pipe.h \ - src/mixer_plugin.h \ - src/mixer_type.h \ - src/mixer/software_mixer_plugin.h \ - src/mixer/pulse_mixer_plugin.h \ - src/daemon.h \ + src/TextInputStream.hxx \ src/AudioCompress/config.h \ src/AudioCompress/compress.h \ - src/buffer.h \ - src/pipe.h \ - src/chunk.h \ - src/path.h \ - src/mapper.h \ src/open.h \ - src/output/httpd_client.h \ - src/output/httpd_internal.h \ - src/page.h \ - src/permission.h \ - src/player_thread.h \ - src/player_control.h \ - src/playlist.h \ - src/playlist_error.h \ - src/playlist_internal.h \ - src/playlist_print.h \ - src/playlist_save.h \ - src/playlist_state.h \ - src/playlist_plugin.h \ - src/playlist_list.h \ - src/playlist_mapper.h \ - src/playlist_any.h \ - src/playlist_song.h \ - src/playlist_queue.h \ - src/playlist_vector.h \ - src/playlist_database.h \ - src/playlist/extm3u_playlist_plugin.h \ - src/playlist/m3u_playlist_plugin.h \ - src/playlist/pls_playlist_plugin.h \ - src/playlist/xspf_playlist_plugin.h \ - src/playlist/asx_playlist_plugin.h \ - src/playlist/rss_playlist_plugin.h \ - src/playlist/lastfm_playlist_plugin.h \ - src/playlist/despotify_playlist_plugin.h \ - src/playlist/cue_playlist_plugin.h \ + src/Playlist.hxx \ src/poison.h \ - src/riff.h \ - src/aiff.h \ - src/queue.h \ - src/queue_print.h \ - src/queue_save.h \ - src/refcount.h \ - src/replay_gain_config.h \ - src/replay_gain_info.h \ - src/replay_gain_ape.h \ - src/sig_handlers.h \ - src/song.h \ - src/song_print.h \ - src/song_save.h \ - src/song_sticker.h \ - src/song_sort.c src/song_sort.h \ - src/socket_util.h \ - src/state_file.h \ - src/stats.h \ - src/sticker.h \ - src/sticker_print.h \ - src/tag.h \ - src/tag_internal.h \ - src/tag_pool.h \ - src/tag_table.h \ - src/tag_ape.h \ - src/tag_id3.h \ - src/tag_rva2.h \ - src/tag_print.h \ - src/tag_save.h \ - src/tokenizer.h \ - src/strset.h \ - src/uri.h \ - src/utils.h \ - src/string_util.h \ - src/volume.h \ - src/zeroconf.h src/zeroconf-internal.h \ - src/locate.h \ - src/stored_playlist.h \ - src/timer.h \ - src/archive_api.h \ - src/archive_internal.h \ - src/archive_list.h \ - src/archive_plugin.h \ - src/archive/bz2_archive_plugin.h \ - src/archive/iso9660_archive_plugin.h \ - src/archive/zzip_archive_plugin.h \ - src/input/archive_input_plugin.h \ - src/mpd_error.h + src/TimePrint.cxx src/TimePrint.hxx \ + src/Timer.hxx src_mpd_SOURCES = \ $(mpd_headers) \ $(DECODER_SRC) \ $(OUTPUT_API_SRC) \ $(MIXER_API_SRC) \ - src/glib_socket.h \ - src/clock.c src/clock.h \ - src/notify.c \ - src/audio_config.c src/audio_config.h \ - src/audio_check.c \ - src/audio_format.c \ - src/audio_parser.c \ - src/protocol/argparser.c src/protocol/argparser.h \ - src/protocol/result.c src/protocol/result.h \ - src/command.c \ - src/idle.c \ - src/cmdline.c \ - src/conf.c \ - src/crossfade.c \ - src/cue/cue_parser.c src/cue/cue_parser.h \ - src/dbUtils.c \ - src/decoder_thread.c \ - src/decoder_control.c \ - src/decoder_api.c \ - src/decoder_internal.c \ - src/decoder_print.c \ - src/directory.c \ - src/directory_save.c \ - src/database.c \ - src/db_internal.h \ - src/db_error.h \ - src/db_lock.c src/db_lock.h \ - src/db_save.c src/db_save.h \ - src/db_print.c src/db_print.h \ - src/db_plugin.h \ - src/db_visitor.h \ - src/db_selection.h \ - src/db/simple_db_plugin.c src/db/simple_db_plugin.h \ - src/exclude.c \ - src/fd_util.c \ - src/fifo_buffer.c src/fifo_buffer.h \ - src/growing_fifo.c src/growing_fifo.h \ - src/filter_config.c \ - src/filter_plugin.c \ - src/filter_registry.c \ - src/update.c \ - src/update_queue.c src/update_queue.h \ - src/update_io.c src/update_io.h \ - src/update_db.c src/update_db.h \ - src/update_walk.c src/update_walk.h \ - src/update_song.c src/update_song.h \ - src/update_container.c src/update_container.h \ - src/update_internal.h \ - src/update_remove.c src/update_remove.h \ - src/client.c \ - src/client_event.c \ - src/client_expire.c \ - src/client_global.c \ - src/client_idle.h \ - src/client_idle.c \ - src/client_list.c \ - src/client_new.c \ - src/client_process.c \ - src/client_read.c \ - src/client_write.c \ - src/client_message.h \ - src/client_message.c \ - src/client_subscribe.h \ - src/client_subscribe.c \ - src/client_file.c src/client_file.h \ - src/server_socket.c \ - src/listen.c \ - src/log.c \ - src/ls.c \ - src/io_thread.c src/io_thread.h \ - src/main.c \ - src/main_win32.c \ - src/event_pipe.c \ - src/daemon.c \ + src/thread/Id.hxx \ + src/thread/Mutex.hxx \ + src/thread/PosixMutex.hxx \ + src/thread/CriticalSection.hxx \ + src/thread/GLibMutex.hxx \ + src/thread/Cond.hxx \ + src/thread/PosixCond.hxx \ + src/thread/WindowsCond.hxx \ + src/thread/GLibCond.hxx \ + src/notify.cxx src/notify.hxx \ + src/AudioConfig.cxx src/AudioConfig.hxx \ + src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \ + src/AudioFormat.cxx src/AudioFormat.hxx \ + src/AudioParser.cxx src/AudioParser.hxx \ + src/protocol/Ack.cxx src/protocol/Ack.hxx \ + src/protocol/ArgParser.cxx src/protocol/ArgParser.hxx \ + src/protocol/Result.cxx src/protocol/Result.hxx \ + src/CommandError.cxx src/CommandError.hxx \ + src/AllCommands.cxx src/AllCommands.hxx \ + src/QueueCommands.cxx src/QueueCommands.hxx \ + src/PlayerCommands.cxx src/PlayerCommands.hxx \ + src/PlaylistCommands.cxx src/PlaylistCommands.hxx \ + src/DatabaseCommands.cxx src/DatabaseCommands.hxx \ + src/OutputCommands.cxx src/OutputCommands.hxx \ + src/MessageCommands.cxx src/MessageCommands.hxx \ + src/OtherCommands.cxx src/OtherCommands.hxx \ + src/Idle.cxx src/Idle.hxx \ + src/CommandLine.cxx src/CommandLine.hxx \ + src/CrossFade.cxx src/CrossFade.hxx \ + src/cue/CueParser.cxx src/cue/CueParser.hxx \ + src/DecoderError.cxx src/DecoderError.hxx \ + src/DecoderThread.cxx src/DecoderThread.hxx \ + src/DecoderCommand.hxx \ + src/DecoderControl.cxx src/DecoderControl.hxx \ + src/DecoderAPI.cxx src/DecoderAPI.hxx \ + src/DecoderPlugin.hxx \ + src/DecoderInternal.cxx src/DecoderInternal.hxx \ + src/DecoderPrint.cxx src/DecoderPrint.hxx \ + src/Directory.cxx src/Directory.hxx \ + src/DirectorySave.cxx src/DirectorySave.hxx \ + src/DatabaseSimple.hxx \ + src/DatabaseGlue.cxx src/DatabaseGlue.hxx \ + src/DatabasePrint.cxx src/DatabasePrint.hxx \ + src/DatabaseQueue.cxx src/DatabaseQueue.hxx \ + src/DatabasePlaylist.cxx src/DatabasePlaylist.hxx \ + src/DatabaseError.cxx src/DatabaseError.hxx \ + src/DatabaseLock.cxx src/DatabaseLock.hxx \ + src/DatabaseSave.cxx src/DatabaseSave.hxx \ + src/DatabasePlugin.hxx \ + src/DatabaseVisitor.hxx \ + src/DatabaseSelection.cxx src/DatabaseSelection.hxx \ + src/ExcludeList.cxx src/ExcludeList.hxx \ + src/FilterConfig.cxx src/FilterConfig.hxx \ + src/FilterPlugin.cxx src/FilterPlugin.hxx \ + src/FilterInternal.hxx \ + src/FilterRegistry.cxx src/FilterRegistry.hxx \ + src/UpdateDomain.cxx src/UpdateDomain.hxx \ + src/UpdateGlue.cxx src/UpdateGlue.hxx \ + src/UpdateQueue.cxx src/UpdateQueue.hxx \ + src/UpdateIO.cxx src/UpdateIO.hxx \ + src/UpdateDatabase.cxx src/UpdateDatabase.hxx \ + src/UpdateWalk.cxx src/UpdateWalk.hxx \ + src/UpdateSong.cxx src/UpdateSong.hxx \ + src/UpdateContainer.cxx src/UpdateContainer.hxx \ + src/UpdateInternal.hxx \ + src/UpdateRemove.cxx src/UpdateRemove.hxx \ + src/CommandListBuilder.cxx src/CommandListBuilder.hxx \ + src/Client.cxx src/Client.hxx \ + src/ClientInternal.hxx \ + src/ClientEvent.cxx \ + src/ClientExpire.cxx \ + src/ClientGlobal.cxx \ + src/ClientIdle.cxx \ + src/ClientList.cxx src/ClientList.hxx \ + src/ClientNew.cxx \ + src/ClientProcess.cxx \ + src/ClientRead.cxx \ + src/ClientWrite.cxx \ + src/ClientMessage.cxx src/ClientMessage.hxx \ + src/ClientSubscribe.cxx src/ClientSubscribe.hxx \ + src/ClientFile.cxx src/ClientFile.hxx \ + src/Listen.cxx src/Listen.hxx \ + src/LogInit.cxx src/LogInit.hxx \ + src/Log.cxx src/Log.hxx src/LogV.hxx \ + src/ls.cxx src/ls.hxx \ + src/IOThread.cxx src/IOThread.hxx \ + src/Main.cxx src/Main.hxx \ + src/Instance.cxx src/Instance.hxx \ + src/Win32Main.cxx \ + src/GlobalEvents.cxx src/GlobalEvents.hxx \ + src/Daemon.cxx src/Daemon.hxx \ src/AudioCompress/compress.c \ - src/buffer.c \ - src/pipe.c \ - src/chunk.c \ - src/path.c \ - src/mapper.c \ - src/page.c \ - src/permission.c \ - src/player_thread.c \ - src/player_control.c \ - src/playlist.c \ - src/playlist_global.c \ - src/playlist_control.c \ - src/playlist_edit.c \ - src/playlist_print.c \ - src/playlist_save.c \ - src/playlist_mapper.c \ - src/playlist_any.c \ - src/playlist_song.c \ - src/playlist_state.c \ - src/playlist_queue.c \ - src/playlist_vector.c \ - src/playlist_database.c \ - src/queue.c \ - src/queue_print.c \ - src/queue_save.c \ - src/replay_gain_config.c \ - src/replay_gain_info.c \ - src/sig_handlers.c \ - src/song.c \ - src/song_update.c \ - src/song_print.c \ - src/song_save.c \ - src/resolver.c src/resolver.h \ - src/socket_util.c \ - src/state_file.c \ - src/stats.c \ - src/tag.c \ - src/tag_pool.c \ - src/tag_print.c \ - src/tag_save.c \ - src/tag_handler.c src/tag_handler.h \ - src/tag_file.c src/tag_file.h \ - src/tokenizer.c \ - src/text_file.c \ - src/text_input_stream.c \ - src/strset.c \ - src/uri.c \ - src/utils.c \ - src/string_util.c \ - src/volume.c \ - src/locate.c \ - src/stored_playlist.c \ - src/timer.c + src/MusicBuffer.cxx src/MusicBuffer.hxx \ + src/MusicPipe.cxx src/MusicPipe.hxx \ + src/MusicChunk.cxx src/MusicChunk.hxx \ + src/Mapper.cxx src/Mapper.hxx \ + src/Page.cxx src/Page.hxx \ + src/Partition.hxx \ + src/Permission.cxx src/Permission.hxx \ + src/PlayerThread.cxx src/PlayerThread.hxx \ + src/PlayerControl.cxx src/PlayerControl.hxx \ + src/Playlist.cxx \ + src/PlaylistError.cxx src/PlaylistError.hxx \ + src/PlaylistGlobal.cxx src/PlaylistGlobal.hxx \ + src/PlaylistControl.cxx \ + src/PlaylistEdit.cxx \ + src/PlaylistPrint.cxx src/PlaylistPrint.hxx \ + src/PlaylistSave.cxx src/PlaylistSave.hxx \ + src/PlaylistMapper.cxx src/PlaylistMapper.hxx \ + src/PlaylistAny.cxx src/PlaylistAny.hxx \ + src/PlaylistSong.cxx src/PlaylistSong.hxx \ + src/PlaylistState.cxx src/PlaylistState.hxx \ + src/PlaylistQueue.cxx src/PlaylistQueue.hxx \ + src/PlaylistVector.cxx src/PlaylistVector.hxx \ + src/PlaylistInfo.hxx \ + src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \ + src/IdTable.hxx \ + src/Queue.cxx src/Queue.hxx \ + src/QueuePrint.cxx src/QueuePrint.hxx \ + src/QueueSave.cxx src/QueueSave.hxx \ + src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \ + src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \ + src/SignalHandlers.cxx src/SignalHandlers.hxx \ + src/Song.cxx src/Song.hxx \ + src/SongUpdate.cxx \ + src/SongPrint.cxx src/SongPrint.hxx \ + src/SongSave.cxx src/SongSave.hxx \ + src/SongSort.cxx src/SongSort.hxx \ + src/StateFile.cxx src/StateFile.hxx \ + src/Stats.cxx src/Stats.hxx \ + src/TagPrint.cxx src/TagPrint.hxx \ + src/TagSave.cxx src/TagSave.hxx \ + src/TagFile.cxx src/TagFile.hxx \ + src/TextFile.cxx src/TextFile.hxx \ + src/TextInputStream.cxx \ + src/Volume.cxx src/Volume.hxx \ + src/SongFilter.cxx src/SongFilter.hxx \ + src/SongPointer.hxx \ + src/PlaylistFile.cxx src/PlaylistFile.hxx \ + src/Timer.cxx # # Windows resource file @@ -371,51 +227,96 @@ endif if ENABLE_DESPOTIFY src_mpd_SOURCES += \ - src/despotify_utils.c + src/DespotifyUtils.cxx src/DespotifyUtils.hxx endif if ENABLE_INOTIFY src_mpd_SOURCES += \ - src/inotify_source.c \ - src/inotify_queue.c \ - src/inotify_update.c + src/InotifyDomain.cxx src/InotifyDomain.hxx \ + src/InotifySource.cxx src/InotifySource.hxx \ + src/InotifyQueue.cxx src/InotifyQueue.hxx \ + src/InotifyUpdate.cxx src/InotifyUpdate.hxx endif if ENABLE_SQLITE src_mpd_SOURCES += \ - src/sticker.c \ - src/sticker_print.c \ - src/song_sticker.c + src/StickerCommands.cxx src/StickerCommands.hxx \ + src/StickerDatabase.cxx src/StickerDatabase.hxx \ + src/StickerPrint.cxx src/StickerPrint.hxx \ + src/SongSticker.cxx src/SongSticker.hxx endif # Generic utility library libutil_a_SOURCES = \ + src/util/Error.cxx src/util/Error.hxx \ + src/util/ReusableArray.hxx \ + src/util/StringUtil.cxx src/util/StringUtil.hxx \ + src/util/Tokenizer.cxx src/util/Tokenizer.hxx \ + src/util/UriUtil.cxx src/util/UriUtil.hxx \ + src/util/Manual.hxx \ + src/util/RefCount.hxx \ + src/util/fifo_buffer.c src/util/fifo_buffer.h \ + src/util/growing_fifo.c src/util/growing_fifo.h \ + src/util/LazyRandomEngine.cxx src/util/LazyRandomEngine.hxx \ + src/util/SliceBuffer.hxx \ + src/util/HugeAllocator.cxx src/util/HugeAllocator.hxx \ + src/util/PeakBuffer.cxx src/util/PeakBuffer.hxx \ src/util/list.h \ src/util/list_sort.c src/util/list_sort.h \ src/util/byte_reverse.c src/util/byte_reverse.h \ src/util/bit_reverse.c src/util/bit_reverse.h +# System library + +libsystem_a_SOURCES = \ + src/system/FatalError.cxx src/system/FatalError.hxx \ + src/system/fd_util.c src/system/fd_util.h \ + src/system/SocketUtil.cxx src/system/SocketUtil.hxx \ + src/system/SocketError.cxx src/system/SocketError.hxx \ + src/system/Resolver.cxx src/system/Resolver.hxx \ + src/system/EventPipe.cxx src/system/EventPipe.hxx \ + src/system/EventFD.cxx src/system/EventFD.hxx \ + src/system/SignalFD.cxx src/system/SignalFD.hxx \ + src/system/EPollFD.cxx src/system/EPollFD.hxx \ + src/system/clock.c src/system/clock.h + +# Event loop library + +libevent_a_SOURCES = \ + src/event/WakeFD.hxx \ + src/event/SignalMonitor.hxx src/event/SignalMonitor.cxx \ + src/event/TimeoutMonitor.hxx src/event/TimeoutMonitor.cxx \ + src/event/IdleMonitor.hxx src/event/IdleMonitor.cxx \ + src/event/DeferredMonitor.hxx src/event/DeferredMonitor.cxx \ + src/event/SocketMonitor.cxx src/event/SocketMonitor.hxx \ + src/event/BufferedSocket.cxx src/event/BufferedSocket.hxx \ + src/event/FullyBufferedSocket.cxx src/event/FullyBufferedSocket.hxx \ + src/event/MultiSocketMonitor.cxx src/event/MultiSocketMonitor.hxx \ + src/event/ServerSocket.cxx src/event/ServerSocket.hxx \ + src/event/Call.hxx src/event/Call.cxx \ + src/event/Loop.cxx src/event/Loop.hxx + # PCM library libpcm_a_SOURCES = \ - src/pcm_buffer.c src/pcm_buffer.h \ - src/pcm_export.c src/pcm_export.h \ - src/pcm_convert.c src/pcm_convert.h \ - src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \ - src/pcm_dsd.c src/pcm_dsd.h \ - src/pcm_dsd_usb.c src/pcm_dsd_usb.h \ - src/pcm_volume.c src/pcm_volume.h \ - src/pcm_mix.c src/pcm_mix.h \ - src/pcm_channels.c src/pcm_channels.h \ - src/pcm_pack.c src/pcm_pack.h \ - src/pcm_format.c src/pcm_format.h \ - src/pcm_resample.c src/pcm_resample.h \ - src/pcm_resample_fallback.c \ - src/pcm_resample_internal.h \ - src/pcm_dither.c src/pcm_dither.h \ - src/pcm_prng.h \ - src/pcm_utils.h + src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \ + src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \ + src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \ + src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h \ + src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \ + src/pcm/PcmDsdUsb.cxx src/pcm/PcmDsdUsb.hxx \ + src/pcm/PcmVolume.cxx src/pcm/PcmVolume.hxx \ + src/pcm/PcmMix.cxx src/pcm/PcmMix.hxx \ + src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \ + src/pcm/pcm_pack.c src/pcm/pcm_pack.h \ + src/pcm/PcmFormat.cxx src/pcm/PcmFormat.hxx \ + src/pcm/PcmResample.cxx src/pcm/PcmResample.hxx \ + src/pcm/PcmResampleFallback.cxx \ + src/pcm/PcmResampleInternal.hxx \ + src/pcm/PcmDither.cxx src/pcm/PcmDither.hxx \ + src/pcm/PcmPrng.hxx \ + src/pcm/PcmUtils.hxx libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(SAMPLERATE_CFLAGS) @@ -424,9 +325,32 @@ PCM_LIBS = \ $(SAMPLERATE_LIBS) if HAVE_LIBSAMPLERATE -libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c +libpcm_a_SOURCES += src/pcm/PcmResampleLibsamplerate.cxx +endif + +# File system library + +libfs_a_SOURCES = \ + src/fs/Path.cxx src/fs/Path.hxx \ + src/fs/FileSystem.cxx src/fs/FileSystem.hxx \ + src/fs/DirectoryReader.hxx + +# database plugins + +libdb_plugins_a_SOURCES = \ + src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \ + src/DatabaseHelpers.cxx src/DatabaseHelpers.hxx \ + src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx + +if HAVE_LIBMPDCLIENT +libdb_plugins_a_SOURCES += \ + src/db/ProxyDatabasePlugin.cxx src/db/ProxyDatabasePlugin.hxx endif +DB_LIBS = \ + libdb_plugins.a \ + $(LIBMPDCLIENT_LIBS) + # archive plugins if ENABLE_ARCHIVE @@ -434,13 +358,15 @@ if ENABLE_ARCHIVE noinst_LIBRARIES += libarchive.a src_mpd_SOURCES += \ - src/update_archive.c src/update_archive.h + src/UpdateArchive.cxx src/UpdateArchive.hxx libarchive_a_SOURCES = \ - src/archive_api.c \ - src/archive_list.c \ - src/archive_plugin.c \ - src/input/archive_input_plugin.c + src/ArchiveLookup.cxx src/ArchiveLookup.hxx \ + src/ArchiveList.cxx src/ArchiveList.hxx \ + src/ArchivePlugin.cxx src/ArchivePlugin.hxx \ + src/ArchiveVisitor.hxx \ + src/ArchiveFile.hxx \ + src/input/ArchiveInputPlugin.cxx src/input/ArchiveInputPlugin.hxx libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(BZ2_CFLAGS) \ $(ISO9660_CFLAGS) \ @@ -453,21 +379,39 @@ ARCHIVE_LIBS = \ $(ZZIP_LIBS) if HAVE_BZ2 -libarchive_a_SOURCES += src/archive/bz2_archive_plugin.c +libarchive_a_SOURCES += \ + src/archive/Bzip2ArchivePlugin.cxx \ + src/archive/Bzip2ArchivePlugin.hxx endif if HAVE_ZZIP -libarchive_a_SOURCES += src/archive/zzip_archive_plugin.c +libarchive_a_SOURCES += \ + src/archive/ZzipArchivePlugin.cxx \ + src/archive/ZzipArchivePlugin.hxx endif if HAVE_ISO9660 -libarchive_a_SOURCES += src/archive/iso9660_archive_plugin.c +libarchive_a_SOURCES += \ + src/archive/Iso9660ArchivePlugin.cxx \ + src/archive/Iso9660ArchivePlugin.hxx endif else ARCHIVE_LIBS = endif +# configuration library + +libconf_a_SOURCES = \ + src/ConfigDefaults.hxx \ + src/ConfigPath.cxx src/ConfigPath.hxx \ + src/ConfigData.cxx src/ConfigData.hxx \ + src/ConfigParser.cxx src/ConfigParser.hxx \ + src/ConfigGlobal.cxx src/ConfigGlobal.hxx \ + src/ConfigFile.cxx src/ConfigFile.hxx \ + src/ConfigTemplates.cxx src/ConfigTemplates.hxx \ + src/ConfigError.cxx src/ConfigError.hxx \ + src/ConfigOption.hxx # tag plugins @@ -478,30 +422,43 @@ TAG_LIBS = \ $(ID3TAG_LIBS) libtag_a_SOURCES =\ - src/ape.c \ - src/replay_gain_ape.c \ - src/tag_ape.c + src/tag/TagType.h \ + src/tag/Tag.cxx src/tag/Tag.hxx \ + src/tag/TagBuilder.cxx src/tag/TagBuilder.hxx \ + src/tag/TagItem.hxx \ + src/tag/TagHandler.cxx src/tag/TagHandler.hxx \ + src/tag/TagSettings.c src/tag/TagSettings.h \ + src/tag/TagConfig.cxx src/tag/TagConfig.hxx \ + src/tag/TagNames.c \ + src/tag/TagString.cxx src/tag/TagString.hxx \ + src/tag/TagPool.cxx src/tag/TagPool.hxx \ + src/tag/TagTable.cxx src/tag/TagTable.hxx \ + src/tag/ApeLoader.cxx src/tag/ApeLoader.hxx \ + src/tag/ApeReplayGain.cxx src/tag/ApeReplayGain.hxx \ + src/tag/ApeTag.cxx src/tag/ApeTag.hxx if HAVE_ID3TAG libtag_a_SOURCES += \ - src/tag_id3.c \ - src/tag_rva2.c \ - src/riff.c src/aiff.c + src/tag/TagId3.cxx src/tag/TagId3.hxx \ + src/tag/TagRva2.cxx src/tag/TagRva2.hxx \ + src/tag/Riff.cxx src/tag/Riff.hxx \ + src/tag/Aiff.cxx src/tag/Aiff.hxx endif # decoder plugins libdecoder_plugins_a_SOURCES = \ - src/decoder/pcm_decoder_plugin.c \ - src/decoder/dsdiff_decoder_plugin.c \ - src/decoder/dsdiff_decoder_plugin.h \ - src/decoder/dsf_decoder_plugin.c \ - src/decoder/dsf_decoder_plugin.h \ - src/decoder/dsdlib.c \ - src/decoder/dsdlib.h \ - src/decoder_buffer.c \ - src/decoder_plugin.c \ - src/decoder_list.c + src/decoder/PcmDecoderPlugin.cxx \ + src/decoder/PcmDecoderPlugin.hxx \ + src/decoder/DsdiffDecoderPlugin.cxx \ + src/decoder/DsdiffDecoderPlugin.hxx \ + src/decoder/DsfDecoderPlugin.cxx \ + src/decoder/DsfDecoderPlugin.hxx \ + src/decoder/DsdLib.cxx \ + src/decoder/DsdLib.hxx \ + src/DecoderBuffer.cxx src/DecoderBuffer.hxx \ + src/DecoderPlugin.cxx \ + src/DecoderList.cxx src/DecoderList.hxx libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ @@ -515,8 +472,10 @@ libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(WAVPACK_CFLAGS) \ $(MAD_CFLAGS) \ $(MPG123_CFLAGS) \ + $(OPUS_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(MPCDEC_CFLAGS) \ + $(ADPLUG_CFLAGS) \ $(FAAD_CFLAGS) DECODER_LIBS = \ @@ -532,70 +491,107 @@ DECODER_LIBS = \ $(WAVPACK_LIBS) \ $(MAD_LIBS) \ $(MPG123_LIBS) \ - $(MP4FF_LIBS) \ + $(OPUS_LIBS) \ $(FFMPEG_LIBS) \ $(MPCDEC_LIBS) \ + $(ADPLUG_LIBS) \ $(FAAD_LIBS) DECODER_SRC = if HAVE_MAD -libdecoder_plugins_a_SOURCES += src/decoder/mad_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/MadDecoderPlugin.cxx \ + src/decoder/MadDecoderPlugin.hxx endif if HAVE_MPG123 -libdecoder_plugins_a_SOURCES += src/decoder/mpg123_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/Mpg123DecoderPlugin.cxx \ + src/decoder/Mpg123DecoderPlugin.hxx endif if HAVE_MPCDEC -libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/MpcdecDecoderPlugin.cxx \ + src/decoder/MpcdecDecoderPlugin.hxx endif -if HAVE_WAVPACK -libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c +if HAVE_OPUS +libdecoder_plugins_a_SOURCES += \ + src/decoder/OggUtil.cxx \ + src/decoder/OggUtil.hxx \ + src/decoder/OggSyncState.hxx \ + src/decoder/OggFind.cxx src/decoder/OggFind.hxx \ + src/decoder/OpusDomain.cxx src/decoder/OpusDomain.hxx \ + src/decoder/OpusReader.hxx \ + src/decoder/OpusHead.hxx \ + src/decoder/OpusHead.cxx \ + src/decoder/OpusTags.cxx \ + src/decoder/OpusTags.hxx \ + src/decoder/OpusDecoderPlugin.cxx \ + src/decoder/OpusDecoderPlugin.h endif -if HAVE_FAAD -libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c +if HAVE_WAVPACK +libdecoder_plugins_a_SOURCES += \ + src/decoder/WavpackDecoderPlugin.cxx \ + src/decoder/WavpackDecoderPlugin.hxx endif -if HAVE_MP4 -libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c +if HAVE_ADPLUG +libdecoder_plugins_a_SOURCES += \ + src/decoder/AdPlugDecoderPlugin.cxx \ + src/decoder/AdPlugDecoderPlugin.h endif -if HAVE_OGG_COMMON -libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.c +if HAVE_FAAD +libdecoder_plugins_a_SOURCES += \ + src/decoder/FaadDecoderPlugin.cxx src/decoder/FaadDecoderPlugin.hxx endif -if HAVE_FLAC_COMMON +if HAVE_XIPH libdecoder_plugins_a_SOURCES += \ - src/decoder/flac_metadata.c \ - src/decoder/flac_pcm.c \ - src/decoder/_flac_common.c + src/decoder/XiphTags.cxx src/decoder/XiphTags.hxx \ + src/decoder/OggCodec.cxx src/decoder/OggCodec.hxx endif if ENABLE_VORBIS_DECODER libdecoder_plugins_a_SOURCES += \ - src/decoder/vorbis_comments.c \ - src/decoder/vorbis_comments.h \ - src/decoder/vorbis_decoder_plugin.c + src/decoder/VorbisDomain.cxx src/decoder/VorbisDomain.hxx \ + src/decoder/VorbisComments.cxx src/decoder/VorbisComments.hxx \ + src/decoder/VorbisDecoderPlugin.cxx src/decoder/VorbisDecoderPlugin.h endif if HAVE_FLAC -libdecoder_plugins_a_SOURCES += src/decoder/flac_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/FlacInput.cxx src/decoder/FlacInput.hxx \ + src/decoder/FlacIOHandle.cxx src/decoder/FlacIOHandle.hxx \ + src/decoder/FlacMetadata.cxx src/decoder/FlacMetadata.hxx \ + src/decoder/FlacPcm.cxx src/decoder/FlacPcm.hxx \ + src/decoder/FlacDomain.cxx src/decoder/FlacDomain.hxx \ + src/decoder/FlacCommon.cxx src/decoder/FlacCommon.hxx \ + src/decoder/FlacDecoderPlugin.cxx \ + src/decoder/FlacDecoderPlugin.h endif if HAVE_AUDIOFILE -libdecoder_plugins_a_SOURCES += src/decoder/audiofile_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/AudiofileDecoderPlugin.cxx \ + src/decoder/AudiofileDecoderPlugin.hxx endif if ENABLE_MIKMOD_DECODER -libdecoder_plugins_a_SOURCES += src/decoder/mikmod_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/MikmodDecoderPlugin.cxx \ + src/decoder/MikmodDecoderPlugin.hxx endif if HAVE_MODPLUG -libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c -libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS) +libmodplug_decoder_plugin_a_SOURCES = \ + src/decoder/ModplugDecoderPlugin.cxx \ + src/decoder/ModplugDecoderPlugin.hxx +libmodplug_decoder_plugin_a_CXXFLAGS = $(AM_CXXFLAGS) $(MODPLUG_CFLAGS) libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS) noinst_LIBRARIES += libmodplug_decoder_plugin.a DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS) @@ -603,30 +599,37 @@ endif if ENABLE_SIDPLAY libdecoder_plugins_a_SOURCES += src/decoder/sidplay_decoder_plugin.cxx -DECODER_SRC += src/dummy.cxx endif if ENABLE_FLUIDSYNTH -libdecoder_plugins_a_SOURCES += src/decoder/fluidsynth_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/FluidsynthDecoderPlugin.cxx \ + src/decoder/FluidsynthDecoderPlugin.hxx endif if ENABLE_WILDMIDI -libdecoder_plugins_a_SOURCES += src/decoder/wildmidi_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/WildmidiDecoderPlugin.cxx \ + src/decoder/WildmidiDecoderPlugin.hxx endif if HAVE_FFMPEG libdecoder_plugins_a_SOURCES += \ - src/decoder/ffmpeg_metadata.c \ - src/decoder/ffmpeg_metadata.h \ - src/decoder/ffmpeg_decoder_plugin.c + src/decoder/FfmpegMetaData.cxx \ + src/decoder/FfmpegMetaData.hxx \ + src/decoder/FfmpegDecoderPlugin.cxx \ + src/decoder/FfmpegDecoderPlugin.hxx endif if ENABLE_SNDFILE -libdecoder_plugins_a_SOURCES += src/decoder/sndfile_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/SndfileDecoderPlugin.cxx \ + src/decoder/SndfileDecoderPlugin.hxx endif if HAVE_GME -libdecoder_plugins_a_SOURCES += src/decoder/gme_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/GmeDecoderPlugin.cxx src/decoder/GmeDecoderPlugin.hxx endif # encoder plugins @@ -639,6 +642,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(LAME_CFLAGS) \ $(TWOLAME_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ + $(OPUS_CFLAGS) \ $(VORBISENC_CFLAGS) ENCODER_LIBS = \ @@ -646,31 +650,49 @@ ENCODER_LIBS = \ $(LAME_LIBS) \ $(TWOLAME_LIBS) \ $(FLAC_LIBS) \ + $(OPUS_LIBS) \ $(VORBISENC_LIBS) -libencoder_plugins_a_SOURCES = - -libencoder_plugins_a_SOURCES += src/encoder_list.c -libencoder_plugins_a_SOURCES += src/encoder/null_encoder.c +libencoder_plugins_a_SOURCES = \ + src/EncoderAPI.hxx \ + src/EncoderPlugin.hxx \ + src/encoder/OggStream.hxx \ + src/encoder/NullEncoderPlugin.cxx src/encoder/NullEncoderPlugin.hxx \ + src/EncoderList.cxx src/EncoderList.hxx if ENABLE_WAVE_ENCODER -libencoder_plugins_a_SOURCES += src/encoder/wave_encoder.c +libencoder_plugins_a_SOURCES += \ + src/encoder/WaveEncoderPlugin.cxx \ + src/encoder/WaveEncoderPlugin.hxx endif if ENABLE_VORBIS_ENCODER -libencoder_plugins_a_SOURCES += src/encoder/vorbis_encoder.c +libencoder_plugins_a_SOURCES += \ + src/encoder/VorbisEncoderPlugin.cxx \ + src/encoder/VorbisEncoderPlugin.hxx +endif + +if HAVE_OPUS +libencoder_plugins_a_SOURCES += \ + src/encoder/OpusEncoderPlugin.cxx \ + src/encoder/OpusEncoderPlugin.hxx endif if ENABLE_LAME_ENCODER -libencoder_plugins_a_SOURCES += src/encoder/lame_encoder.c +libencoder_plugins_a_SOURCES += \ + src/encoder/LameEncoderPlugin.cxx \ + src/encoder/LameEncoderPlugin.hxx endif if ENABLE_TWOLAME_ENCODER -libencoder_plugins_a_SOURCES += src/encoder/twolame_encoder.c +libencoder_plugins_a_SOURCES += \ + src/encoder/TwolameEncoderPlugin.cxx \ + src/encoder/TwolameEncoderPlugin.hxx endif if ENABLE_FLAC_ENCODER -libencoder_plugins_a_SOURCES += src/encoder/flac_encoder.c +libencoder_plugins_a_SOURCES += \ + src/encoder/FlacEncoderPlugin.cxx src/encoder/FlacEncoderPlugin.hxx endif else @@ -679,14 +701,16 @@ endif if HAVE_ZEROCONF -src_mpd_SOURCES += src/zeroconf.c +src_mpd_SOURCES += \ + src/ZeroconfInternal.hxx \ + src/ZeroconfGlue.cxx src/ZeroconfGlue.hxx if HAVE_AVAHI -src_mpd_SOURCES += src/zeroconf-avahi.c +src_mpd_SOURCES += src/ZeroconfAvahi.cxx src/ZeroconfAvahi.hxx endif if HAVE_BONJOUR -src_mpd_SOURCES += src/zeroconf-bonjour.c +src_mpd_SOURCES += src/ZeroconfBonjour.cxx src/ZeroconfBonjour.hxx endif endif @@ -695,16 +719,16 @@ endif # libinput_a_SOURCES = \ - src/input_init.c \ - src/input_registry.c \ - src/input_stream.c \ - src/input_internal.c src/input_internal.h \ - src/input/rewind_input_plugin.c \ - src/input/file_input_plugin.c + src/InputInit.cxx src/InputInit.hxx \ + src/InputRegistry.cxx src/InputRegistry.hxx \ + src/InputStream.cxx src/InputStream.hxx \ + src/InputPlugin.hxx \ + src/InputInternal.cxx src/InputInternal.hxx \ + src/input/RewindInputPlugin.cxx src/input/RewindInputPlugin.hxx \ + src/input/FileInputPlugin.cxx src/input/FileInputPlugin.hxx libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(CURL_CFLAGS) \ - $(SOUP_CFLAGS) \ $(CDIO_PARANOIA_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(DESPOTIFY_CFLAGS) \ @@ -713,44 +737,43 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ INPUT_LIBS = \ libinput.a \ $(CURL_LIBS) \ - $(SOUP_LIBS) \ $(CDIO_PARANOIA_LIBS) \ $(FFMPEG_LIBS) \ $(DESPOTIFY_LIBS) \ $(MMS_LIBS) if ENABLE_CURL -libinput_a_SOURCES += src/input/curl_input_plugin.c \ - src/icy_metadata.c -endif - -if ENABLE_SOUP libinput_a_SOURCES += \ - src/input/soup_input_plugin.c \ - src/input/soup_input_plugin.h + src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \ + src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx endif if ENABLE_CDIO_PARANOIA -libinput_a_SOURCES += src/input/cdio_paranoia_input_plugin.c +libinput_a_SOURCES += \ + src/input/CdioParanoiaInputPlugin.cxx \ + src/input/CdioParanoiaInputPlugin.hxx endif if HAVE_FFMPEG -libinput_a_SOURCES += src/input/ffmpeg_input_plugin.c +libinput_a_SOURCES += \ + src/input/FfmpegInputPlugin.cxx src/input/FfmpegInputPlugin.hxx endif if ENABLE_MMS -libinput_a_SOURCES += src/input/mms_input_plugin.c +libinput_a_SOURCES += \ + src/input/MmsInputPlugin.cxx src/input/MmsInputPlugin.hxx endif if ENABLE_DESPOTIFY -libinput_a_SOURCES += src/input/despotify_input_plugin.c +libinput_a_SOURCES += \ + src/input/DespotifyInputPlugin.cxx \ + src/input/DespotifyInputPlugin.hxx endif liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(AO_CFLAGS) \ $(ALSA_CFLAGS) \ - $(FFADO_CFLAGS) \ $(JACK_CFLAGS) \ $(OPENAL_CFLAGS) \ $(OPENSSL_CFLAGS) \ @@ -763,134 +786,134 @@ OUTPUT_LIBS = \ $(AO_LIBS) \ $(ALSA_LIBS) \ $(ROAR_LIBS) \ - $(FFADO_LIBS) \ $(JACK_LIBS) \ $(OPENAL_LIBS) \ $(PULSE_LIBS) \ $(SHOUT_LIBS) OUTPUT_API_SRC = \ - src/output_list.c \ - src/output_all.c \ - src/output_thread.c \ - src/output_control.c \ - src/output_state.c \ - src/output_print.c \ - src/output_command.c \ - src/output_plugin.c src/output_plugin.h \ - src/output_finish.c \ - src/output_init.c + src/OutputAPI.hxx \ + src/OutputInternal.hxx \ + src/OutputList.cxx src/OutputList.hxx \ + src/OutputAll.cxx src/OutputAll.hxx \ + src/OutputThread.cxx src/OutputThread.hxx \ + src/OutputError.cxx src/OutputError.hxx \ + src/OutputControl.cxx src/OutputControl.hxx \ + src/OutputState.cxx src/OutputState.hxx \ + src/OutputPrint.cxx src/OutputPrint.hxx \ + src/OutputCommand.cxx src/OutputCommand.hxx \ + src/OutputPlugin.cxx src/OutputPlugin.hxx \ + src/OutputFinish.cxx \ + src/OutputInit.cxx liboutput_plugins_a_SOURCES = \ - src/output/null_output_plugin.h \ - src/output/null_output_plugin.c + src/output/NullOutputPlugin.cxx \ + src/output/NullOutputPlugin.hxx MIXER_LIBS = \ libmixer_plugins.a \ $(PULSE_LIBS) MIXER_API_SRC = \ - src/mixer_control.c \ - src/mixer_type.c \ - src/mixer_all.c \ - src/mixer_api.c + src/MixerPlugin.hxx \ + src/MixerList.hxx \ + src/MixerControl.cxx src/MixerControl.hxx \ + src/MixerType.cxx src/MixerType.hxx \ + src/MixerAll.cxx src/MixerAll.hxx \ + src/MixerInternal.hxx libmixer_plugins_a_SOURCES = \ - src/mixer/software_mixer_plugin.c + src/mixer/SoftwareMixerPlugin.cxx \ + src/mixer/SoftwareMixerPlugin.hxx libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(ALSA_CFLAGS) \ $(PULSE_CFLAGS) if HAVE_ALSA liboutput_plugins_a_SOURCES += \ - src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/alsa_mixer_plugin.c + src/output/AlsaOutputPlugin.cxx \ + src/output/AlsaOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/AlsaMixerPlugin.cxx endif if HAVE_ROAR liboutput_plugins_a_SOURCES += \ - src/output/roar_output_plugin.c src/output/roar_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/roar_mixer_plugin.c -endif - -if ENABLE_FFADO_OUTPUT -liboutput_plugins_a_SOURCES += \ - src/output/ffado_output_plugin.c src/output/ffado_output_plugin.h + src/output/RoarOutputPlugin.cxx src/output/RoarOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/RoarMixerPlugin.cxx endif if HAVE_AO liboutput_plugins_a_SOURCES += \ - src/output/ao_output_plugin.c src/output/ao_output_plugin.h + src/output/AoOutputPlugin.cxx src/output/AoOutputPlugin.hxx endif if HAVE_FIFO liboutput_plugins_a_SOURCES += \ - src/output/fifo_output_plugin.c src/output/fifo_output_plugin.h + src/output/FifoOutputPlugin.cxx src/output/FifoOutputPlugin.hxx endif if ENABLE_PIPE_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/pipe_output_plugin.c src/output/pipe_output_plugin.h + src/output/PipeOutputPlugin.cxx src/output/PipeOutputPlugin.hxx endif if HAVE_JACK liboutput_plugins_a_SOURCES += \ - src/output/jack_output_plugin.c src/output/jack_output_plugin.h -endif - -if HAVE_MVP -liboutput_plugins_a_SOURCES += \ - src/output/mvp_output_plugin.c src/output/mvp_output_plugin.h + src/output/JackOutputPlugin.cxx src/output/JackOutputPlugin.hxx endif if HAVE_OSS liboutput_plugins_a_SOURCES += \ - src/output/oss_output_plugin.c src/output/oss_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c + src/output/OssOutputPlugin.cxx \ + src/output/OssOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/OssMixerPlugin.cxx endif if HAVE_OPENAL liboutput_plugins_a_SOURCES += \ - src/output/openal_output_plugin.c src/output/openal_output_plugin.h + src/output/OpenALOutputPlugin.cxx src/output/OpenALOutputPlugin.hxx endif if HAVE_OSX liboutput_plugins_a_SOURCES += \ - src/output/osx_output_plugin.c src/output/osx_output_plugin.h + src/output/OSXOutputPlugin.cxx \ + src/output/OSXOutputPlugin.hxx endif if HAVE_PULSE liboutput_plugins_a_SOURCES += \ - src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/pulse_mixer_plugin.c + src/output/PulseOutputPlugin.cxx src/output/PulseOutputPlugin.hxx +libmixer_plugins_a_SOURCES += \ + src/mixer/PulseMixerPlugin.cxx src/mixer/PulseMixerPlugin.hxx endif if HAVE_SHOUT liboutput_plugins_a_SOURCES += \ - src/output/shout_output_plugin.c src/output/shout_output_plugin.h + src/output/ShoutOutputPlugin.cxx src/output/ShoutOutputPlugin.hxx endif if ENABLE_RECORDER_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/recorder_output_plugin.c src/output/recorder_output_plugin.h + src/output/RecorderOutputPlugin.cxx src/output/RecorderOutputPlugin.hxx endif if ENABLE_HTTPD_OUTPUT liboutput_plugins_a_SOURCES += \ - src/icy_server.c \ - src/output/httpd_client.c \ - src/output/httpd_output_plugin.c src/output/httpd_output_plugin.h + src/IcyMetaDataServer.cxx src/IcyMetaDataServer.hxx \ + src/output/HttpdInternal.hxx \ + src/output/HttpdClient.cxx src/output/HttpdClient.hxx \ + src/output/HttpdOutputPlugin.cxx src/output/HttpdOutputPlugin.hxx endif if ENABLE_SOLARIS_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/solaris_output_plugin.c src/output/solaris_output_plugin.h + src/output/SolarisOutputPlugin.cxx src/output/SolarisOutputPlugin.hxx endif if ENABLE_WINMM_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/winmm_mixer_plugin.c + src/output/WinmmOutputPlugin.cxx src/output/WinmmOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/WinmmMixerPlugin.cxx endif @@ -899,16 +922,26 @@ endif # libplaylist_plugins_a_SOURCES = \ - src/playlist/extm3u_playlist_plugin.c \ - src/playlist/m3u_playlist_plugin.c \ - src/playlist/pls_playlist_plugin.c \ - src/playlist/xspf_playlist_plugin.c \ - src/playlist/asx_playlist_plugin.c \ - src/playlist/rss_playlist_plugin.c \ - src/playlist/cue_playlist_plugin.c \ - src/playlist/embcue_playlist_plugin.c \ - src/playlist/embcue_playlist_plugin.h \ - src/playlist_list.c + src/PlaylistPlugin.hxx \ + src/SongEnumerator.hxx \ + src/MemorySongEnumerator.cxx src/MemorySongEnumerator.hxx \ + src/playlist/ExtM3uPlaylistPlugin.cxx \ + src/playlist/ExtM3uPlaylistPlugin.hxx \ + src/playlist/M3uPlaylistPlugin.cxx \ + src/playlist/M3uPlaylistPlugin.hxx \ + src/playlist/PlsPlaylistPlugin.cxx \ + src/playlist/PlsPlaylistPlugin.hxx \ + src/playlist/XspfPlaylistPlugin.cxx \ + src/playlist/XspfPlaylistPlugin.hxx \ + src/playlist/AsxPlaylistPlugin.cxx \ + src/playlist/AsxPlaylistPlugin.hxx \ + src/playlist/RssPlaylistPlugin.cxx \ + src/playlist/RssPlaylistPlugin.hxx \ + src/playlist/CuePlaylistPlugin.cxx \ + src/playlist/CuePlaylistPlugin.hxx \ + src/playlist/EmbeddedCuePlaylistPlugin.cxx \ + src/playlist/EmbeddedCuePlaylistPlugin.hxx \ + src/PlaylistRegistry.cxx src/PlaylistRegistry.hxx libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(YAJL_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) @@ -917,18 +950,16 @@ PLAYLIST_LIBS = \ libplaylist_plugins.a \ $(FLAC_LIBS) -if ENABLE_LASTFM -libplaylist_plugins_a_SOURCES += src/playlist/lastfm_playlist_plugin.c -endif - if ENABLE_DESPOTIFY -libplaylist_plugins_a_SOURCES += src/playlist/despotify_playlist_plugin.c +libplaylist_plugins_a_SOURCES += \ + src/playlist/DespotifyPlaylistPlugin.cxx \ + src/playlist/DespotifyPlaylistPlugin.hxx endif if ENABLE_SOUNDCLOUD libplaylist_plugins_a_SOURCES += \ - src/playlist/soundcloud_playlist_plugin.h \ - src/playlist/soundcloud_playlist_plugin.c + src/playlist/SoundCloudPlaylistPlugin.cxx \ + src/playlist/SoundCloudPlaylistPlugin.hxx PLAYLIST_LIBS += $(YAJL_LIBS) endif @@ -937,14 +968,19 @@ endif # libfilter_plugins_a_SOURCES = \ - src/filter/null_filter_plugin.c \ - src/filter/chain_filter_plugin.c \ - src/filter/autoconvert_filter_plugin.c \ - src/filter/convert_filter_plugin.c \ - src/filter/route_filter_plugin.c \ - src/filter/normalize_filter_plugin.c \ - src/filter/replay_gain_filter_plugin.c \ - src/filter/volume_filter_plugin.c + src/filter/NullFilterPlugin.cxx \ + src/filter/ChainFilterPlugin.cxx \ + src/filter/ChainFilterPlugin.hxx \ + src/filter/AutoConvertFilterPlugin.cxx \ + src/filter/AutoConvertFilterPlugin.hxx \ + src/filter/ConvertFilterPlugin.cxx \ + src/filter/ConvertFilterPlugin.hxx \ + src/filter/RouteFilterPlugin.cxx \ + src/filter/NormalizeFilterPlugin.cxx \ + src/filter/ReplayGainFilterPlugin.cxx \ + src/filter/ReplayGainFilterPlugin.hxx \ + src/filter/VolumeFilterPlugin.cxx \ + src/filter/VolumeFilterPlugin.hxx FILTER_LIBS = \ libfilter_plugins.a \ @@ -998,6 +1034,7 @@ noinst_PROGRAMS = \ $(C_TESTS) \ test/read_conf \ test/run_resolver \ + test/DumpDatabase \ test/run_input \ test/dump_text_file \ test/dump_playlist \ @@ -1009,6 +1046,10 @@ noinst_PROGRAMS = \ test/run_normalize \ test/software_volume +if ENABLE_ARCHIVE +noinst_PROGRAMS += test/visit_archive +endif + if HAVE_ID3TAG noinst_PROGRAMS += test/dump_rva2 endif @@ -1019,37 +1060,98 @@ noinst_PROGRAMS += test/read_mixer endif test_read_conf_LDADD = \ + libconf.a \ + libutil.a \ + libsystem.a \ + libfs.a \ $(GLIB_LIBS) -test_read_conf_SOURCES = test/read_conf.c \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c +test_read_conf_SOURCES = \ + src/Log.cxx \ + test/read_conf.cxx test_run_resolver_LDADD = \ + libsystem.a \ + libutil.a \ + $(GLIB_LIBS) +test_run_resolver_SOURCES = \ + src/Log.cxx \ + test/run_resolver.cxx + +test_DumpDatabase_LDADD = \ + $(DB_LIBS) \ + $(TAG_LIBS) \ + libconf.a \ + libutil.a \ + libsystem.a \ + libfs.a \ $(GLIB_LIBS) -test_run_resolver_SOURCES = test/run_resolver.c \ - src/resolver.c +test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ + src/Log.cxx \ + src/DatabaseError.cxx \ + src/DatabaseRegistry.cxx \ + src/DatabaseSelection.cxx \ + src/Directory.cxx src/DirectorySave.cxx \ + src/PlaylistVector.cxx src/PlaylistDatabase.cxx \ + src/DatabaseLock.cxx src/DatabaseSave.cxx \ + src/Song.cxx src/SongSave.cxx src/SongSort.cxx \ + src/TagSave.cxx \ + src/SongFilter.cxx \ + src/TextFile.cxx test_run_input_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ + $(TAG_LIBS) \ + libconf.a \ + libutil.a \ + libevent.a \ + libsystem.a \ + libfs.a \ $(GLIB_LIBS) -test_run_input_SOURCES = test/run_input.c \ +test_run_input_SOURCES = test/run_input.cxx \ test/stdbin.h \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/tag.c src/tag_pool.c src/tag_save.c \ - src/fd_util.c + src/Log.cxx \ + src/IOThread.cxx \ + src/TagSave.cxx + +if ENABLE_ARCHIVE + +test_visit_archive_LDADD = \ + $(INPUT_LIBS) \ + $(ARCHIVE_LIBS) \ + $(TAG_LIBS) \ + libconf.a \ + libutil.a \ + libevent.a \ + libsystem.a \ + libfs.a \ + $(GLIB_LIBS) +test_visit_archive_SOURCES = test/visit_archive.cxx \ + src/Log.cxx \ + src/IOThread.cxx \ + src/InputStream.cxx + +if ENABLE_DESPOTIFY +test_visit_archive_SOURCES += src/DespotifyUtils.cxx +endif + +endif test_dump_text_file_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ + $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ + libsystem.a \ + libutil.a \ $(GLIB_LIBS) -test_dump_text_file_SOURCES = test/dump_text_file.c \ +test_dump_text_file_SOURCES = test/dump_text_file.cxx \ test/stdbin.h \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/tag.c src/tag_pool.c \ - src/text_input_stream.c src/fifo_buffer.c \ - src/fd_util.c + src/Log.cxx \ + src/IOThread.cxx \ + src/TextInputStream.cxx test_dump_playlist_LDADD = \ $(PLAYLIST_LIBS) \ @@ -1058,24 +1160,27 @@ test_dump_playlist_LDADD = \ $(ARCHIVE_LIBS) \ $(DECODER_LIBS) \ $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libsystem.a \ + libfs.a \ libutil.a \ + libpcm.a \ $(GLIB_LIBS) -test_dump_playlist_SOURCES = test/dump_playlist.c \ +test_dump_playlist_SOURCES = test/dump_playlist.cxx \ $(DECODER_SRC) \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ - src/uri.c \ - src/song.c src/tag.c src/tag_pool.c src/tag_save.c \ - src/tag_handler.c src/tag_file.c \ - src/audio_check.c src/pcm_buffer.c \ - src/text_input_stream.c src/fifo_buffer.c \ - src/cue/cue_parser.c src/cue/cue_parser.h \ - src/fd_util.c + src/Log.cxx \ + src/IOThread.cxx \ + src/Song.cxx src/TagSave.cxx \ + src/TagFile.cxx \ + src/CheckAudioFormat.cxx \ + src/TextInputStream.cxx \ + src/cue/CueParser.cxx src/cue/CueParser.hxx if HAVE_FLAC test_dump_playlist_SOURCES += \ - src/replay_gain_info.c \ - src/decoder/flac_metadata.c + src/ReplayGainInfo.cxx \ + src/decoder/FlacMetadata.cxx endif test_run_decoder_LDADD = \ @@ -1084,18 +1189,18 @@ test_run_decoder_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libsystem.a \ + libfs.a \ libutil.a \ $(GLIB_LIBS) -test_run_decoder_SOURCES = test/run_decoder.c \ +test_run_decoder_SOURCES = test/run_decoder.cxx \ test/stdbin.h \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/tag.c src/tag_pool.c src/tag_handler.c \ - src/replay_gain_info.c \ - src/uri.c \ - src/fd_util.c \ - src/audio_check.c \ - src/audio_format.c \ + src/Log.cxx \ + src/IOThread.cxx \ + src/ReplayGainInfo.cxx \ + src/AudioFormat.cxx src/CheckAudioFormat.cxx \ $(ARCHIVE_SRC) \ $(INPUT_SRC) \ $(TAG_SRC) \ @@ -1107,167 +1212,172 @@ test_read_tags_LDADD = \ $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libsystem.a \ + libfs.a \ libutil.a \ $(GLIB_LIBS) -test_read_tags_SOURCES = test/read_tags.c \ - src/io_thread.c src/io_thread.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/tag.c src/tag_pool.c src/tag_handler.c \ - src/replay_gain_info.c \ - src/uri.c \ - src/fd_util.c \ - src/audio_check.c \ +test_read_tags_SOURCES = test/read_tags.cxx \ + src/Log.cxx \ + src/IOThread.cxx \ + src/ReplayGainInfo.cxx \ + src/CheckAudioFormat.cxx \ $(DECODER_SRC) if HAVE_ID3TAG test_dump_rva2_LDADD = \ - $(ID3TAG_LIBS) \ + $(TAG_LIBS) \ + libutil.a \ $(GLIB_LIBS) -test_dump_rva2_SOURCES = test/dump_rva2.c \ - src/riff.c src/aiff.c \ - src/tag_handler.c \ - src/tag_id3.c \ - src/tag_rva2.c +test_dump_rva2_SOURCES = \ + src/Log.cxx \ + test/dump_rva2.cxx endif test_run_filter_LDADD = \ $(FILTER_LIBS) \ + libconf.a \ + libutil.a \ + libsystem.a \ + libfs.a \ $(GLIB_LIBS) -test_run_filter_SOURCES = test/run_filter.c \ +test_run_filter_SOURCES = test/run_filter.cxx \ + test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/filter_plugin.c \ - src/filter_registry.c \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c \ - src/audio_check.c \ - src/audio_format.c \ - src/audio_parser.c \ - src/replay_gain_config.c \ - src/replay_gain_info.c \ + src/Log.cxx \ + src/FilterPlugin.cxx src/FilterRegistry.cxx \ + src/CheckAudioFormat.cxx \ + src/AudioFormat.cxx \ + src/AudioParser.cxx \ + src/ReplayGainInfo.cxx \ src/AudioCompress/compress.c if ENABLE_DESPOTIFY -test_read_tags_SOURCES += \ - src/despotify_utils.c -test_run_input_SOURCES += \ - src/despotify_utils.c -test_dump_text_file_SOURCES += \ - src/despotify_utils.c -test_dump_playlist_SOURCES += \ - src/despotify_utils.c -test_run_decoder_SOURCES += \ - src/despotify_utils.c +test_read_tags_SOURCES += src/DespotifyUtils.cxx +test_run_input_SOURCES += src/DespotifyUtils.cxx +test_dump_text_file_SOURCES += src/DespotifyUtils.cxx +test_dump_playlist_SOURCES += src/DespotifyUtils.cxx +test_run_decoder_SOURCES += src/DespotifyUtils.cxx endif if ENABLE_ENCODER noinst_PROGRAMS += test/run_encoder -test_run_encoder_SOURCES = test/run_encoder.c \ +test_run_encoder_SOURCES = test/run_encoder.cxx \ test/stdbin.h \ - src/fifo_buffer.c src/growing_fifo.c \ - src/conf.c src/tokenizer.c \ - src/utils.c src/string_util.c \ - src/tag.c src/tag_pool.c \ - src/audio_check.c \ - src/audio_format.c \ - src/audio_parser.c + src/Log.cxx \ + src/CheckAudioFormat.cxx \ + src/AudioFormat.cxx \ + src/AudioParser.cxx test_run_encoder_LDADD = \ $(ENCODER_LIBS) \ - libpcm.a \ $(TAG_LIBS) \ + libconf.a \ + libpcm.a \ + libsystem.a \ + libfs.a \ + libutil.a \ $(GLIB_LIBS) endif if ENABLE_VORBIS_ENCODER noinst_PROGRAMS += test/test_vorbis_encoder -test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \ +test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \ test/stdbin.h \ - src/conf.c src/tokenizer.c \ - src/utils.c \ - src/string_util.c \ - src/tag.c src/tag_pool.c \ - src/audio_check.c \ - src/audio_format.c \ - src/audio_parser.c \ - src/pcm_buffer.c \ - src/fifo_buffer.c src/growing_fifo.c \ + src/Log.cxx \ + src/CheckAudioFormat.cxx \ + src/AudioFormat.cxx \ + src/AudioParser.cxx \ $(ENCODER_SRC) test_test_vorbis_encoder_CPPFLAGS = $(AM_CPPFLAGS) \ $(ENCODER_CFLAGS) test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \ $(ENCODER_LIBS) \ + $(PCM_LIBS) \ + $(TAG_LIBS) \ + libconf.a \ + libsystem.a \ + libfs.a \ + libutil.a \ $(GLIB_LIBS) endif -test_software_volume_SOURCES = test/software_volume.c \ +test_software_volume_SOURCES = test/software_volume.cxx \ test/stdbin.h \ - src/audio_check.c \ - src/audio_parser.c + src/CheckAudioFormat.cxx \ + src/AudioParser.cxx test_software_volume_LDADD = \ $(PCM_LIBS) \ + libutil.a \ $(GLIB_LIBS) -test_run_normalize_SOURCES = test/run_normalize.c \ +test_run_normalize_SOURCES = test/run_normalize.cxx \ test/stdbin.h \ - src/audio_check.c \ - src/audio_parser.c \ + src/CheckAudioFormat.cxx \ + src/AudioParser.cxx \ src/AudioCompress/compress.c test_run_normalize_LDADD = \ + libutil.a \ $(GLIB_LIBS) -test_run_convert_SOURCES = test/run_convert.c \ - src/dsd2pcm/dsd2pcm.c \ - src/fifo_buffer.c \ - src/audio_format.c \ - src/audio_check.c \ - src/audio_parser.c +test_run_convert_SOURCES = test/run_convert.cxx \ + src/Log.cxx \ + src/AudioFormat.cxx \ + src/CheckAudioFormat.cxx \ + src/AudioParser.cxx test_run_convert_LDADD = \ $(PCM_LIBS) \ libutil.a \ $(GLIB_LIBS) test_run_output_LDADD = $(MPD_LIBS) \ + $(PCM_LIBS) \ $(OUTPUT_LIBS) \ $(ENCODER_LIBS) \ libmixer_plugins.a \ $(FILTER_LIBS) \ + $(TAG_LIBS) \ + libconf.a \ + libevent.a \ + libfs.a \ + libsystem.a \ libutil.a \ $(GLIB_LIBS) -test_run_output_SOURCES = test/run_output.c \ +test_run_output_SOURCES = test/run_output.cxx \ + test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/io_thread.c src/io_thread.h \ - src/audio_check.c \ - src/audio_format.c \ - src/audio_parser.c \ - src/timer.c src/clock.c \ - src/tag.c src/tag_pool.c \ - src/fifo_buffer.c src/growing_fifo.c \ - src/page.c \ - src/socket_util.c \ - src/resolver.c \ - src/output_init.c src/output_finish.c src/output_list.c \ - src/output_plugin.c \ - src/mixer_api.c \ - src/mixer_control.c \ - src/mixer_type.c \ - src/filter_plugin.c \ - src/filter_config.c \ + src/Log.cxx \ + src/IOThread.cxx \ + src/CheckAudioFormat.cxx \ + src/AudioFormat.cxx \ + src/AudioParser.cxx \ + src/Timer.cxx \ + src/Page.cxx \ + src/OutputError.cxx \ + src/OutputInit.cxx src/OutputFinish.cxx src/OutputList.cxx \ + src/OutputPlugin.cxx \ + src/MixerControl.cxx \ + src/MixerType.cxx \ + src/FilterPlugin.cxx \ + src/FilterConfig.cxx \ src/AudioCompress/compress.c \ - src/replay_gain_info.c \ - src/replay_gain_config.c \ - src/fd_util.c \ - src/server_socket.c + src/ReplayGainInfo.cxx test_read_mixer_LDADD = \ libpcm.a \ libmixer_plugins.a \ $(OUTPUT_LIBS) \ + libconf.a \ + libutil.a \ + libevent.a \ + libsystem.a \ + libfs.a \ $(GLIB_LIBS) -test_read_mixer_SOURCES = test/read_mixer.c \ - src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ - src/mixer_control.c src/mixer_api.c \ - src/filter_plugin.c \ - src/filter/volume_filter_plugin.c \ - src/fd_util.c +test_read_mixer_SOURCES = test/read_mixer.cxx \ + src/Log.cxx \ + src/MixerControl.cxx \ + src/FilterPlugin.cxx \ + src/filter/VolumeFilterPlugin.cxx if ENABLE_BZIP2_TEST TESTS += test/test_archive_bzip2.sh @@ -1283,11 +1393,15 @@ endif if ENABLE_INOTIFY noinst_PROGRAMS += test/run_inotify -test_run_inotify_SOURCES = test/run_inotify.c \ - src/fd_util.c \ - src/fifo_buffer.c \ - src/inotify_source.c -test_run_inotify_LDADD = $(GLIB_LIBS) +test_run_inotify_SOURCES = test/run_inotify.cxx \ + src/Log.cxx \ + src/InotifyDomain.cxx \ + src/InotifySource.cxx +test_run_inotify_LDADD = \ + libevent.a \ + libsystem.a \ + libutil.a \ + $(GLIB_LIBS) endif test_test_byte_reverse_SOURCES = \ @@ -1297,32 +1411,35 @@ test_test_byte_reverse_LDADD = \ $(GLIB_LIBS) test_test_pcm_SOURCES = \ - test/test_pcm_dither.c \ - test/test_pcm_pack.c \ - test/test_pcm_channels.c \ - test/test_pcm_volume.c \ - test/test_pcm_all.h \ - test/test_pcm_main.c + test/test_pcm_util.hxx \ + test/test_pcm_dither.cxx \ + test/test_pcm_pack.cxx \ + test/test_pcm_channels.cxx \ + test/test_pcm_format.cxx \ + test/test_pcm_volume.cxx \ + test/test_pcm_mix.cxx \ + test/test_pcm_all.hxx \ + test/test_pcm_main.cxx test_test_pcm_LDADD = \ $(PCM_LIBS) \ libutil.a \ $(GLIB_LIBS) test_test_queue_priority_SOURCES = \ - src/queue.c \ - test/test_queue_priority.c + src/Queue.cxx \ + test/test_queue_priority.cxx test_test_queue_priority_LDADD = \ + libsystem.a \ + libutil.a \ $(GLIB_LIBS) -if HAVE_CXX -noinst_PROGRAMS += src/dsd2pcm/dsd2pcm +noinst_PROGRAMS += src/pcm/dsd2pcm/dsd2pcm -src_dsd2pcm_dsd2pcm_SOURCES = \ - src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \ - src/dsd2pcm/noiseshape.c src/dsd2pcm/noiseshape.h \ - src/dsd2pcm/main.cpp -src_dsd2pcm_dsd2pcm_LDADD = libutil.a -endif +src_pcm_dsd2pcm_dsd2pcm_SOURCES = \ + src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h \ + src/pcm/dsd2pcm/noiseshape.c src/pcm/dsd2pcm/noiseshape.h \ + src/pcm/dsd2pcm/main.cpp +src_pcm_dsd2pcm_dsd2pcm_LDADD = libutil.a endif @@ -1,3 +1,33 @@ +ver 0.18 (2012/??/??) +* configuration: + - allow tilde paths for socket +* protocol: + - new command "toggleoutput" + - search for album artist falls back to the artist tag +* input: + - curl: enable https + - soup: plugin removed +* playlist: + - lastfm: remove defunct Last.fm support +* decoder: + - adplug: new decoder plugin using libadplug + - ffmpeg: drop support for pre-0.8 ffmpeg + - flac: require libFLAC 1.2 or newer + - flac: support FLAC files inside archives + - opus: new decoder plugin for the Opus codec + - vorbis: skip 16 bit quantisation, provide float samples + - mp4ff: obsolete plugin removed +* encoder: + - opus: new encoder plugin for the Opus codec + - vorbis: accept floating point input samples +* output: + - new option "tags" may be used to disable sending tags to output + - alsa: workaround for noise after manual song change + - ffado: remove broken plugin + - mvp: remove obsolete plugin +* improved decoder/output error reporting +* eliminate timer wakeup on idle MPD + ver 0.17.6 (2013/10/14) * mixer: - alsa: fix busy loop when USB sound device gets unplugged diff --git a/configure.ac b/configure.ac index d9fd30115..87ffd5cbe 100644 --- a/configure.ac +++ b/configure.ac @@ -1,19 +1,19 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.17.6, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.18~git, musicpd-dev-team@lists.sourceforge.net) VERSION_MAJOR=0 -VERSION_MINOR=17 +VERSION_MINOR=18 VERSION_REVISION=0 VERSION_EXTRA=0 -AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_SRCDIR([src/Main.cxx]) AM_INIT_AUTOMAKE([foreign 1.11 dist-bzip2 dist-xz subdir-objects]) AM_SILENT_RULES AC_CONFIG_HEADERS(config.h) AC_CONFIG_MACRO_DIR([m4]) -AC_DEFINE(PROTOCOL_VERSION, "0.17.0", [The MPD protocol version]) +AC_DEFINE(PROTOCOL_VERSION, "0.18.0", [The MPD protocol version]) dnl --------------------------------------------------------------------------- @@ -28,22 +28,6 @@ AN_PROGRAM([ar], [AC_PROG_AR]) AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL(AR, ar, :)]) AC_PROG_AR -HAVE_CXX=yes -if test x$CXX = xg++; then - # CXX=g++ probably means that autoconf hasn't found any C++ - # compiler; to be sure, we check again - AC_PATH_PROG(CXX, $CXX, no) - if test x$CXX = xno; then - # no, we don't have C++ - the following hack is - # required because automake insists on using $(CXX) - # for linking the MPD binary - AC_MSG_NOTICE([Disabling C++ support]) - CXX="$CC" - HAVE_CXX=no - fi -fi -AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes) - AC_PROG_INSTALL AC_PROG_MAKE_SET PKG_PROG_PKG_CONFIG @@ -87,7 +71,8 @@ mingw32* | windows*) src/win/mpd_win32_rc.rc ]) AC_CHECK_TOOL(WINDRES, windres) - AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501" + AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN" + AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0600 -D_WIN32_WINNT=0x0600" LIBS="$LIBS -lws2_32" HAVE_WINDOWS=1 ;; @@ -131,6 +116,19 @@ if test -z "$prefix" || test "x$prefix" = xNONE; then fi dnl --------------------------------------------------------------------------- +dnl Language Checks +dnl --------------------------------------------------------------------------- + +AC_CXX_COMPILE_STDCXX_0X +if test "$ax_cv_cxx_compile_cxx0x_native" != yes; then + if test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then + AM_CXXFLAGS="$AM_CXXFLAGS -std=gnu++0x" + elif test "$ax_cv_cxx_compile_cxx0x_cxx" = yes; then + AM_CXXFLAGS="$AM_CXXFLAGS -std=c++0x" + fi +fi + +dnl --------------------------------------------------------------------------- dnl Header/Library Checks dnl --------------------------------------------------------------------------- AC_CHECK_FUNCS(daemon fork) @@ -142,6 +140,9 @@ AC_SEARCH_LIBS([socket], [socket]) AC_SEARCH_LIBS([gethostbyname], [nsl]) AC_CHECK_FUNCS(pipe2 accept4) +MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD) +MPD_OPTIONAL_FUNC(signalfd, signalfd, USE_SIGNALFD) +MPD_OPTIONAL_FUNC(epoll, epoll_create1, USE_EPOLL) AC_SEARCH_LIBS([exp], [m],, [AC_MSG_ERROR([exp() not found])]) @@ -152,6 +153,17 @@ AC_CHECK_HEADERS(valgrind/memcheck.h) dnl --------------------------------------------------------------------------- dnl Allow tools to be specifically built dnl --------------------------------------------------------------------------- + +AC_ARG_ENABLE(libmpdclient, + AS_HELP_STRING([--enable-libmpdclient], + [enable support for the MPD client]),, + enable_libmpdclient=auto) + +AC_ARG_ENABLE(adplug, + AS_HELP_STRING([--enable-adplug], + [enable the AdPlug decoder plugin (default: auto)]),, + enable_adplug=auto) + AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, [enable_alsa=auto]) @@ -186,11 +198,6 @@ AC_ARG_ENABLE(curl, [enable support for libcurl HTTP streaming (default: auto)]),, [enable_curl=auto]) -AC_ARG_ENABLE(soup, - AS_HELP_STRING([--enable-soup], - [enable support for libsoup HTTP streaming (default: auto)]),, - [enable_soup=auto]) - AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debugging (default: disabled)]),, @@ -201,10 +208,6 @@ AC_ARG_ENABLE(documentation, [build documentation (default: disable)]),, [enable_documentation=no]) -AC_ARG_ENABLE(ffado, - AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),, - [enable_ffado=no]) - AC_ARG_ENABLE(ffmpeg, AS_HELP_STRING([--enable-ffmpeg], [enable FFMPEG support]),, @@ -262,11 +265,6 @@ AC_ARG_ENABLE(jack, AC_SYS_LARGEFILE -AC_ARG_ENABLE(lastfm, - AS_HELP_STRING([--enable-lastfm], - [enable support for last.fm radio (default: disable)]),, - [enable_lastfm=no]) - AC_ARG_ENABLE(despotify, AS_HELP_STRING([--enable-despotify], [enable support for despotify (default: disable)]),, @@ -321,16 +319,16 @@ AC_ARG_ENABLE(mpg123, [enable libmpg123 decoder plugin]),, enable_mpg123=auto) -AC_ARG_ENABLE(mvp, - AS_HELP_STRING([--enable-mvp], - [enable support for Hauppauge Media MVP (default: disable)]),, - enable_mvp=no) - AC_ARG_ENABLE(openal, AS_HELP_STRING([--enable-openal], [enable OpenAL support (default: disable)]),, enable_openal=no) +AC_ARG_ENABLE(opus, + AS_HELP_STRING([--enable-opus], + [enable Opus codec support (default: auto)]),, + enable_opus=auto) + AC_ARG_ENABLE(oss, AS_HELP_STRING([--disable-oss], [disable OSS support (default: enable)]),, @@ -461,8 +459,8 @@ AC_ARG_WITH(tremor-includes, dnl --------------------------------------------------------------------------- dnl Mandatory Libraries dnl --------------------------------------------------------------------------- -PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.16 gthread-2.0],, - [AC_MSG_ERROR([GLib 2.16 is required])]) +PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.24 gthread-2.0],, + [AC_MSG_ERROR([GLib 2.24 is required])]) if test x$GCC = xyes; then # suppress warnings in the GLib headers @@ -542,6 +540,15 @@ dnl --------------------------------------------------------------------------- dnl Miscellaneous Libraries dnl --------------------------------------------------------------------------- +dnl -------------------------------- libmpdclient -------------------------------- +MPD_AUTO_PKG(libmpdclient, LIBMPDCLIENT, [libmpdclient >= 2.2], + [MPD client library], [libmpdclient not found]) +if test x$enable_libmpdclient = xyes; then + AC_DEFINE(HAVE_LIBMPDCLIENT, 1, [Define to use libmpdclient]) +fi + +AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes) + dnl --------------------------------- inotify --------------------------------- AC_CHECK_FUNCS(inotify_init inotify_init1) @@ -557,6 +564,27 @@ AM_CONDITIONAL(ENABLE_INOTIFY, test x$enable_inotify = xyes) dnl --------------------------------- libwrap --------------------------------- if test x$enable_libwrap != xno; then AC_CHECK_LIBWRAP(found_libwrap=yes, found_libwrap=no) + + if test x$found_libwrap = xyes; then + dnl See if libwrap is compatible with C++; it is + dnl broken on many systems + AC_MSG_CHECKING(whether libwrap is compatible with C++) + AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([ + #include <tcpd.h> + bool CheckLibWrap(int fd, const char &progname) { + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + fromhost(&req); + return hosts_access(&req); + } + ])], + AC_MSG_RESULT([yes]), + [found_libwrap=no; AC_MSG_RESULT([no]); + AC_MSG_WARN([Your version of libwrap is broken with C++])]) + AC_LANG_POP + fi + MPD_AUTO_RESULT(libwrap, libwrap, [libwrap not found]) fi @@ -674,31 +702,13 @@ dnl Input Plugins dnl --------------------------------------------------------------------------- dnl ----------------------------------- CURL ---------------------------------- -MPD_AUTO_PKG(curl, CURL, [libcurl], +MPD_AUTO_PKG(curl, CURL, [libcurl >= 7.18], [libcurl HTTP streaming], [libcurl not found]) if test x$enable_curl = xyes; then AC_DEFINE(ENABLE_CURL, 1, [Define when libcurl is used for HTTP streaming]) fi AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes) -dnl ----------------------------------- SOUP ---------------------------------- -MPD_AUTO_PKG(soup, SOUP, [libsoup-2.4], - [libsoup HTTP streaming], [libsoup not found]) -if test x$enable_soup = xyes; then - AC_DEFINE(ENABLE_SOUP, 1, [Define when libsoup is used for HTTP streaming]) -fi -AM_CONDITIONAL(ENABLE_SOUP, test x$enable_soup = xyes) - -dnl --------------------------------- Last.FM --------------------------------- -if test x$enable_lastfm = xyes; then - if test x$enable_curl != xyes; then - AC_MSG_ERROR([Cannot enable last.fm radio without curl]) - fi - - AC_DEFINE(ENABLE_LASTFM, 1, [Define when last.fm radio is enabled]) -fi -AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes) - dnl --------------------------------- Despotify --------------------------------- MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify], [Despotify support], [despotify not found]) @@ -811,6 +821,14 @@ dnl --------------------------------------------------------------------------- dnl Decoder Plugins dnl --------------------------------------------------------------------------- +dnl -------------------------------- libadplug -------------------------------- +MPD_AUTO_PKG(adplug, ADPLUG, [adplug], + [AdPlug decoder plugin], [libadplug not found]) +if test x$enable_adplug = xyes; then + AC_DEFINE(HAVE_ADPLUG, 1, [Define to use libadplug]) +fi +AM_CONDITIONAL(HAVE_ADPLUG, test x$enable_adplug = xyes) + dnl -------------------------------- audiofile -------------------------------- MPD_AUTO_PKG(audiofile, AUDIOFILE, [audiofile >= 0.1.7], [audiofile decoder plugin], [libaudiofile not found]) @@ -823,10 +841,9 @@ dnl ----------------------------------- FAAD ---------------------------------- AM_PATH_FAAD() AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes) -AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes) dnl ---------------------------------- ffmpeg --------------------------------- -MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52.31 libavcodec >= 52.20 libavutil >= 49.15], +MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 53.17 libavcodec >= 53.25 libavutil >= 51.17], [ffmpeg decoder library], [libavformat+libavcodec+libavutil not found]) if test x$enable_ffmpeg = xyes; then @@ -837,7 +854,7 @@ AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes) dnl ----------------------------------- FLAC ---------------------------------- -MPD_AUTO_PKG(flac, FLAC, [flac >= 1.1], +MPD_AUTO_PKG(flac, FLAC, [flac >= 1.2], [FLAC decoder], [libFLAC not found]) if test x$enable_flac = xyes; then @@ -904,9 +921,6 @@ fi AM_CONDITIONAL(ENABLE_MIKMOD_DECODER, test x$enable_mikmod = xyes) dnl -------------------------------- libmodplug ------------------------------- -found_modplug=$HAVE_CXX -MPD_AUTO_PRE(modplug, [modplug decoder plugin], [No C++ compiler found]) - MPD_AUTO_PKG(modplug, MODPLUG, [libmodplug], [modplug decoder plugin], [libmodplug not found]) @@ -915,6 +929,14 @@ if test x$enable_modplug = xyes; then fi AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes) +dnl -------------------------------- libopus ---------------------------------- +MPD_AUTO_PKG(opus, OPUS, [opus ogg], + [opus decoder plugin], [libopus not found]) +if test x$enable_opus = xyes; then + AC_DEFINE(HAVE_OPUS, 1, [Define to use libopus]) +fi +AM_CONDITIONAL(HAVE_OPUS, test x$enable_opus = xyes) + dnl -------------------------------- libsndfile ------------------------------- dnl See above test, which may disable this. MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile], @@ -937,16 +959,7 @@ if test x$enable_mpc = xyes; then LIBS=$oldlibs CPPFLAGS=$oldcppflags - if test x$enable_mpc = xyes; then - AC_CHECK_HEADER([mpc/mpcdec.h], - [AC_DEFINE(HAVE_MPCDEC,1, - [Define to use libmpcdec for MPC decoding])], - [AC_CHECK_HEADER(mpcdec/mpcdec.h, - [AC_DEFINE(MPC_IS_OLD_API, 1, - [Define if an old pre-SV8 libmpcdec is used])] - )] - ) - else + if test x$enable_mpc != xyes; then AC_MSG_WARN([mpcdec lib needed for MPC support -- disabling MPC support]) fi fi @@ -1020,9 +1033,6 @@ fi AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enable_tremor = xyes) dnl --------------------------------- sidplay --------------------------------- -found_sidplay=$HAVE_CXX -MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found]) - if test x$enable_sidplay != xno; then # we're not using pkg-config here # because libsidplay2's .pc file requires libtool @@ -1097,9 +1107,9 @@ if test x$enable_mad = xno && test x$enable_mikmod = xno; then test x$enable_modplug = xno && - test x$enable_mp4 = xno && test x$enable_mpc = xno && test x$enable_mpg123 = xno && + test x$enable_opus = xno && test x$enable_sidplay = xno && test x$enable_tremor = xno && test x$enable_vorbis = xno && @@ -1109,11 +1119,8 @@ if AC_MSG_ERROR([No input plugins supported!]) fi -AM_CONDITIONAL(HAVE_OGG_COMMON, - test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes) - -AM_CONDITIONAL(HAVE_FLAC_COMMON, - test x$enable_flac = xyes) +AM_CONDITIONAL(HAVE_XIPH, + test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes || test x$enable_opus = xyes) dnl --------------------------------------------------------------------------- dnl Encoders for Streaming Audio Output Plugins @@ -1201,6 +1208,7 @@ fi dnl --------------------------- encoder plugins test -------------------------- if test x$enable_vorbis_encoder != xno || + test x$enable_opus != xno || test x$enable_lame_encoder != xno || test x$enable_twolame_encoder != xno || test x$enable_flac_encoder != xno || @@ -1246,17 +1254,6 @@ fi AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes) -dnl ----------------------------------- FFADO --------------------------------- - -MPD_AUTO_PKG(ffado, FFADO, [libffado], - [libffado output plugin], [libffado not found]) - -if test x$enable_ffado = xyes; then - AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin]) -fi - -AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes) - dnl ----------------------------------- FIFO ---------------------------------- if test x$enable_fifo = xyes; then AC_CHECK_FUNC([mkfifo], @@ -1312,13 +1309,6 @@ fi AM_CONDITIONAL(HAVE_AO, test x$enable_ao = xyes) -dnl ----------------------------------- MVP ----------------------------------- -if test x$enable_mvp = xyes; then - AC_DEFINE(HAVE_MVP,1,[Define to enable Hauppauge Media MVP support]) -fi - -AM_CONDITIONAL(HAVE_MVP, test x$enable_mvp = xyes) - dnl ---------------------------------- OpenAL --------------------------------- AC_SUBST(OPENAL_CFLAGS,"") AC_SUBST(OPENAL_LIBS,"") @@ -1370,7 +1360,7 @@ fi AM_CONDITIONAL(ENABLE_PIPE_OUTPUT, test x$enable_pipe_output = xyes) dnl -------------------------------- PulseAudio ------------------------------- -MPD_AUTO_PKG(pulse, PULSE, [libpulse], +MPD_AUTO_PKG(pulse, PULSE, [libpulse >= 0.9.16], [PulseAudio output plugin], [libpulse not found]) if test x$enable_pulse = xyes; then AC_DEFINE([HAVE_PULSE], 1, @@ -1454,11 +1444,9 @@ if test x$enable_alsa = xno && test x$enable_roar = xno && test x$enable_ao = xno && - test x$enable_ffado = xno && test x$enable_fifo = xno && test x$enable_httpd_output = xno && test x$enable_jack = xno && - test x$enable_mvp = xno && test x$enable_openal = xno && test x$enable_oss = xno && test x$enable_osx = xno && @@ -1504,6 +1492,22 @@ dnl --------------------------------------------------------------------------- dnl ---------------------------------- debug ---------------------------------- if test "x$enable_debug" = xno; then AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG" + + AX_APPEND_COMPILE_FLAGS([-ffunction-sections]) + AX_APPEND_COMPILE_FLAGS([-fdata-sections]) + AX_APPEND_COMPILE_FLAGS([-fvisibility=hidden]) + + AC_LANG_PUSH([C++]) + AX_APPEND_COMPILE_FLAGS([-ffunction-sections]) + AX_APPEND_COMPILE_FLAGS([-fdata-sections]) + AX_APPEND_COMPILE_FLAGS([-fvisibility=hidden]) + AX_APPEND_COMPILE_FLAGS([-fno-threadsafe-statics]) + AX_APPEND_COMPILE_FLAGS([-fmerge-all-constants]) + AX_APPEND_COMPILE_FLAGS([-fno-exceptions]) + AX_APPEND_COMPILE_FLAGS([-fno-rtti]) + AC_LANG_POP + + AX_APPEND_LINK_FLAGS([-Wl,--gc-sections]) fi dnl ----------------------------------- GCC ----------------------------------- @@ -1518,6 +1522,17 @@ then AX_APPEND_COMPILE_FLAGS([-Wcast-qual]) AX_APPEND_COMPILE_FLAGS([-Wwrite-strings]) AX_APPEND_COMPILE_FLAGS([-pedantic]) + + AC_LANG_PUSH([C++]) + AX_APPEND_COMPILE_FLAGS([-Wall]) + AX_APPEND_COMPILE_FLAGS([-Wextra]) + AX_APPEND_COMPILE_FLAGS([-Wmissing-declarations]) + AX_APPEND_COMPILE_FLAGS([-Wshadow]) + AX_APPEND_COMPILE_FLAGS([-Wpointer-arith]) + AX_APPEND_COMPILE_FLAGS([-Wcast-qual]) + AX_APPEND_COMPILE_FLAGS([-Wwrite-strings]) + AX_APPEND_COMPILE_FLAGS([-Wsign-compare]) + AC_LANG_POP fi dnl ---------------------------- warnings as errors --------------------------- @@ -1550,20 +1565,21 @@ results(un,[UNIX Domain Sockets]) printf '\nFile format support:\n\t' results(aac, [AAC]) +results(adplug, [AdPlug]) results(sidplay, [C64 SID]) results(ffmpeg, [FFMPEG]) results(flac, [FLAC]) results(fluidsynth, [FluidSynth]) results(gme, [GME]) -results(sndfile, [libsndfile]) printf '\n\t' +results(sndfile, [libsndfile]) results(mikmod, [MikMod]) results(modplug, [MODPLUG]) results(mad, [MAD]) results(mpg123, [MPG123]) -results(mp4, [MP4]) results(mpc, [Musepack]) printf '\n\t' +results(opus, [Opus]) results(tremor, [OggTremor]) results(vorbis, [OggVorbis]) results(audiofile, [WAVE]) @@ -1572,6 +1588,7 @@ results(wildmidi, [WildMidi]) printf '\nOther features:\n\t' results(lsr, [libsamplerate]) +results(libmpdclient, [libmpdclient]) results(inotify, [inotify]) results(sqlite, [SQLite]) @@ -1580,14 +1597,12 @@ results(id3,[ID3]) printf '\nPlayback support:\n\t' results(alsa,ALSA) -results(ffado,FFADO) results(fifo,FIFO) results(recorder_output,[File Recorder]) results(httpd_output,[HTTP Daemon]) results(jack,[JACK]) printf '\n\t' results(ao,[libao]) -results(mvp, [Media MVP]) results(oss,[OSS]) results(openal,[OpenAL]) results(osx, [OS X]) @@ -1607,6 +1622,7 @@ if results(flac_encoder, [FLAC]) results(lame_encoder, [LAME]) results(vorbis_encoder, [Ogg Vorbis]) + results(opus, [Opus]) results(twolame_encoder, [TwoLAME]) results(wave_encoder, [WAVE]) fi @@ -1615,11 +1631,9 @@ printf '\nStreaming support:\n\t' results(cdio_paranoia, [CDIO_PARANOIA]) results(curl,[CURL]) results(despotify,[Despotify]) -results(lastfm,[Last.FM]) results(soundcloud,[Soundcloud]) printf '\n\t' results(mms,[MMS]) -results(soup, [SOUP]) printf '\n\n##########################################\n\n' diff --git a/doc/developer.xml b/doc/developer.xml index 010b85064..eb318fa5a 100644 --- a/doc/developer.xml +++ b/doc/developer.xml @@ -61,7 +61,7 @@ foo(const char *abc, int xyz) { if (abc == NULL) { - g_warning("Foo happened!\n"); + LogWarning("Foo happened!"); return -1; } diff --git a/doc/doxygen.conf.in b/doc/doxygen.conf.in index 95dca9a3c..1d9d0ca7c 100644 --- a/doc/doxygen.conf.in +++ b/doc/doxygen.conf.in @@ -1158,7 +1158,7 @@ INCLUDE_FILE_PATTERNS = # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = G_GNUC_UNUSED= +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. @@ -12,8 +12,8 @@ stores info about all available music, and this info can be easily searched and retrieved. Player control, info retrieval, and playlist management can all be managed remotely. -MPD searches for a config file in \fB~/.mpdconf\fP then \fB/etc/mpd.conf\fP or -uses CONF_FILE. +MPD searches for a config file in \fB$XDG_CONFIG_HOME/mpd/mpd.conf\fP then +\fB~/.mpdconf\fP then \fB/etc/mpd.conf\fP or uses CONF_FILE. Read more about MPD at <\fBhttp://www.musicpd.org/\fP>. .SH OPTIONS diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index 62f5565e1..b3a46e157 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -3,8 +3,9 @@ mpd.conf \- Music Player Daemon configuration file .SH DESCRIPTION \fBmpd.conf\fP is the configuration file for mpd(1). If not specified on the -command line, MPD first searches for it at \fB~/.mpdconf\fP then at -\fB~/.mpd/mpd.conf\fP and then in \fB/etc/mpd.conf\fP. +command line, MPD first searches for it at \fB$XDG_CONFIG_HOME/mpd/mpd.conf\fP +then at \fB~/.mpdconf\fP then at \fB~/.mpd/mpd.conf\fP and then in +\fB/etc/mpd.conf\fP. Lines beginning with a "#" character are comments. All other non-empty lines specify parameters and their values. These lines contain the parameter name @@ -76,8 +77,9 @@ You can set a port that is different from the global port setting, e.g. "localhost:6602". IPv6 addresses must be enclosed in square brackets if you want to configure a port, e.g. "[::1]:6602". -To bind to a Unix domain socket, specify an absolute path. For a -system-wide MPD, we suggest the path "\fB/var/run/mpd/socket\fP". +To bind to a Unix domain socket, specify an absolute path or a path starting +with a tilde (~). For a system-wide MPD, we suggest the path +"\fB/var/run/mpd/socket\fP". .TP .B port <port> This specifies the port that mpd listens on. The default is 6600. diff --git a/doc/protocol.xml b/doc/protocol.xml index c03388a24..50e58db63 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -1876,6 +1876,20 @@ OK </para> </listitem> </varlistentry> + <varlistentry id="command_toggleoutput"> + <term> + <cmdsynopsis> + <command>toggleoutput</command> + <arg choice="req"><replaceable>ID</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Turns an output on or off, depending on the current + state. + </para> + </listitem> + </varlistentry> <varlistentry id="command_outputs"> <term> <cmdsynopsis> diff --git a/doc/user.xml b/doc/user.xml index 38d8a9d85..61d02c162 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -165,6 +165,53 @@ systemctl start mpd.socket</programlisting> </section> <section> + <title>Configuring database plugins</title> + + <para> + If a music directory is configured, one database plugin is + used. To configure this plugin, add a + <varname>database</varname> block to + <filename>mpd.conf</filename>: + </para> + + <programlisting>database { + plugin "simple" + path "/var/lib/mpd/db" +} + </programlisting> + + <para> + The following table lists the <varname>database</varname> + options valid for all plugins: + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry> + Name + </entry> + <entry> + Description + </entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>plugin</varname> + </entry> + <entry> + The name of the plugin. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title>Configuring input plugins</title> <para> @@ -173,7 +220,7 @@ systemctl start mpd.socket</programlisting> </para> <programlisting>input { - plugin "lastfm" + plugin "despotify" user "foo" password "bar" } @@ -391,6 +438,18 @@ systemctl start mpd.socket</programlisting> </row> <row> <entry> + <varname>tags</varname> + <parameter>yes|no</parameter> + </entry> + <entry> + If set to "no", then MPD will not send tags to this + output. This is only useful for output plugins that + can receive tags, for example the + <varname>httpd</varname> output plugin. + </entry> + </row> + <row> + <entry> <varname>always_on</varname> <parameter>yes|no</parameter> </entry> @@ -618,6 +677,78 @@ systemctl start mpd.socket</programlisting> <title>Plugin reference</title> <section> + <title>Database plugins</title> + + <section> + <title><varname>simple</varname></title> + + <para> + The default plugin. Stores a copy of the database in + memory. A file is used for permanent storage. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>path</varname> + </entry> + <entry> + The path of the database file. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>proxy</varname></title> + + <para> + Provides access to the database of another MPD instance + using <filename>libmpdclient</filename>. Experimental! + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>host</varname> + </entry> + <entry> + The host name of the "master" MPD instance. + </entry> + </row> + <row> + <entry> + <varname>port</varname> + </entry> + <entry> + The port number of the "master" MPD instance. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + </section> + + <section> <title>Input plugins</title> <section> @@ -744,35 +875,6 @@ systemctl start mpd.socket</programlisting> </tgroup> </informaltable> </section> - - <section> - <title><varname>soup</varname></title> - - <para> - Opens remote files or streams over HTTP. - </para> - - <informaltable> - <tgroup cols="2"> - <thead> - <row> - <entry>Setting</entry> - <entry>Description</entry> - </row> - </thead> - <tbody> - <row> - <entry> - <varname>proxy</varname> - </entry> - <entry> - Sets the address of the HTTP proxy server. - </entry> - </row> - </tbody> - </tgroup> - </informaltable> - </section> </section> <section> @@ -1241,43 +1343,6 @@ systemctl start mpd.socket</programlisting> </section> <section> - <title><varname>ffado</varname></title> - - <para> - The <varname>ffado</varname> plugin connects to FireWire - audio devices via <filename>libffado</filename>. - </para> - - <para> - Warning: this plugin was not tested successfully. I just - couldn't keep libffado2 from crashing. Use at your own - risk. - </para> - - <informaltable> - <tgroup cols="2"> - <thead> - <row> - <entry>Setting</entry> - <entry>Description</entry> - </row> - </thead> - <tbody> - <row> - <entry> - <varname>device</varname> - <parameter>NAME</parameter> - </entry> - <entry> - Sets the device which should be used, e.g. "hw:0". - </entry> - </row> - </tbody> - </tgroup> - </informaltable> - </section> - - <section> <title><varname>jack</varname></title> <para> @@ -1362,16 +1427,6 @@ systemctl start mpd.socket</programlisting> </section> <section> - <title><varname>mvp</varname></title> - - <para> - The <varname>mvp</varname> plugin uses the proprietary - Hauppauge Media MVP interface. We do not know any user of - this plugin, and we do not know if it actually works. - </para> - </section> - - <section> <title><varname>httpd</varname></title> <para> @@ -1889,45 +1944,6 @@ systemctl start mpd.socket</programlisting> <title>Playlist plugins</title> <section> - <title><varname>lastfm</varname></title> - - <para> - Plays last.fm radio. - </para> - - <informaltable> - <tgroup cols="2"> - <thead> - <row> - <entry>Setting</entry> - <entry>Description</entry> - </row> - </thead> - <tbody> - <row> - <entry> - <varname>user</varname> - <parameter>USERNAME</parameter> - </entry> - <entry> - The last.fm user name. - </entry> - </row> - <row> - <entry> - <varname>password</varname> - <parameter>PWD</parameter> - </entry> - <entry> - The last.fm password. - </entry> - </row> - </tbody> - </tgroup> - </informaltable> - </section> - - <section> <title><varname>embcue</varname></title> <para> diff --git a/m4/ax_append_link_flags.m4 b/m4/ax_append_link_flags.m4 new file mode 100644 index 000000000..4fc433700 --- /dev/null +++ b/m4/ax_append_link_flags.m4 @@ -0,0 +1,61 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_append_link_flags.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# For every FLAG1, FLAG2 it is checked whether the linker works with the +# flag. If it does, the flag is added FLAGS-VARIABLE +# +# If FLAGS-VARIABLE is not specified, the linker's flags (LDFLAGS) is +# used. During the check the flag is always added to the linker's flags. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: This macro depends on the AX_APPEND_FLAG and AX_CHECK_LINK_FLAG. +# Please keep this macro in sync with AX_APPEND_COMPILE_FLAGS. +# +# LICENSE +# +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_APPEND_LINK_FLAGS], +[for flag in $1; do + AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3]) +done +])dnl AX_APPEND_LINK_FLAGS diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4 new file mode 100644 index 000000000..e2d0d363e --- /dev/null +++ b/m4/ax_check_link_flag.m4 @@ -0,0 +1,71 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the linker or gives an error. +# (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_LINK_FLAG], +[AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl +AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ + ax_check_save_flags=$LDFLAGS + LDFLAGS="$LDFLAGS $4 $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + LDFLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_LINK_FLAGS diff --git a/m4/ax_cxx_compile_stdcxx_0x.m4 b/m4/ax_cxx_compile_stdcxx_0x.m4 new file mode 100644 index 000000000..a4e556ff9 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_0x.m4 @@ -0,0 +1,107 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_0x.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_0X +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++0x +# standard. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 7 + +AU_ALIAS([AC_CXX_COMPILE_STDCXX_0X], [AX_CXX_COMPILE_STDCXX_0X]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX_0X], [ + AC_CACHE_CHECK(if g++ supports C++0x features without additional flags, + ax_cv_cxx_compile_cxx0x_native, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_native=yes, ax_cv_cxx_compile_cxx0x_native=no) + AC_LANG_RESTORE + ]) + + AC_CACHE_CHECK(if g++ supports C++0x features with -std=c++0x, + ax_cv_cxx_compile_cxx0x_cxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=c++0x" + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_cxx=yes, ax_cv_cxx_compile_cxx0x_cxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + AC_CACHE_CHECK(if g++ supports C++0x features with -std=gnu++0x, + ax_cv_cxx_compile_cxx0x_gxx, + [AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -std=gnu++0x" + AC_TRY_COMPILE([ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c);],, + ax_cv_cxx_compile_cxx0x_gxx=yes, ax_cv_cxx_compile_cxx0x_gxx=no) + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + if test "$ax_cv_cxx_compile_cxx0x_native" = yes || + test "$ax_cv_cxx_compile_cxx0x_cxx" = yes || + test "$ax_cv_cxx_compile_cxx0x_gxx" = yes; then + AC_DEFINE(HAVE_STDCXX_0X,,[Define if g++ supports C++0x features. ]) + fi +]) diff --git a/m4/faad.m4 b/m4/faad.m4 index 1048c566c..5ca520e79 100644 --- a/m4/faad.m4 +++ b/m4/faad.m4 @@ -8,38 +8,14 @@ AC_ARG_ENABLE(aac, [disable AAC support (default: enable)]),, enable_aac=yes) -AC_ARG_WITH(faad, - AS_HELP_STRING([--with-faad=PFX], - [prefix where faad2 is installed (optional)]),, - faad_prefix="") -AC_ARG_WITH(faad-libraries, - AS_HELP_STRING([--with-faad-libraries=DIR], - [directory where faad2 library is installed (optional)]),, - faad_libraries="") -AC_ARG_WITH(faad-includes, - AS_HELP_STRING([--with-faad-includes=DIR], - [directory where faad2 header files are installed (optional)]),, - faad_includes="") - if test x$enable_aac = xyes; then - if test "x$faad_libraries" != "x" ; then - FAAD_LIBS="-L$faad_libraries" - elif test "x$faad_prefix" != "x" ; then - FAAD_LIBS="-L$faad_prefix/lib" - fi - - FAAD_LIBS="$FAAD_LIBS -lfaad" - - if test "x$faad_includes" != "x" ; then - FAAD_CFLAGS="-I$faad_includes" - elif test "x$faad_prefix" != "x" ; then - FAAD_CFLAGS="-I$faad_prefix/include" - fi + FAAD_LIBS="-lfaad" + FAAD_CFLAGS="" oldcflags=$CFLAGS oldlibs=$LIBS oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $FAAD_CFLAGS -I." + CFLAGS="$CFLAGS $FAAD_CFLAGS" LIBS="$LIBS $FAAD_LIBS" CPPFLAGS=$CFLAGS AC_CHECK_HEADER(faad.h,,enable_aac=no) @@ -47,77 +23,36 @@ if test x$enable_aac = xyes; then AC_CHECK_DECL(FAAD2_VERSION,,enable_aac=no,[#include <faad.h>]) fi if test x$enable_aac = xyes; then - AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>]) - fi - if test x$enable_aac = xyes; then - AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no) - if test x$enable_aac = xno; then - enable_aac=yes - AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) - fi + AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) fi if test x$enable_aac = xyes; then - AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen) - AC_COMPILE_IFELSE([AC_LANG_SOURCE([ -#include <faad.h> - -int main() { - char buffer; - long bufferlen = 0; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; - unsigned char channels; - long sampleRate; - mp4AudioSpecificConfig mp4ASC; - - decoder = faacDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder,config); - AudioSpecificConfig(&buffer, bufferlen, &mp4ASC); - faacDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecDecode(decoder,&frameInfo,&buffer,bufferlen); - - return 0; -} -])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no); AC_MSG_CHECKING(that FAAD2 can even be used) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include <faad.h> int main() { char buffer; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; + NeAACDecHandle decoder; + NeAACDecFrameInfo frameInfo; + NeAACDecConfigurationPtr config; unsigned char channels; long sampleRate; long bufferlen = 0; - unsigned long dummy1_32; - unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8, - dummy7_8, dummy8_8; - decoder = faacDecOpen(); - config = faacDecGetCurrentConfiguration(decoder); + decoder = NeAACDecOpen(); + config = NeAACDecGetCurrentConfiguration(decoder); config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder,config); - AudioSpecificConfig(&buffer,&dummy1_32,&dummy2_8, - &dummy3_8,&dummy4_8,&dummy5_8, - &dummy6_8,&dummy7_8,&dummy8_8); - faacDecInit(decoder,&buffer,&sampleRate,&channels); - faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); - faacDecDecode(decoder,&frameInfo,&buffer); - faacDecClose(decoder); + NeAACDecSetConfiguration(decoder,config); + NeAACDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); + NeAACDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); + NeAACDecDecode(decoder,&frameInfo,&buffer,bufferlen); + NeAACDecClose(decoder); return 0; } ])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no]) - ]) fi if test x$enable_aac = xyes; then - AC_CHECK_MEMBERS([faacDecConfiguration.downMatrix,faacDecConfiguration.dontUpSampleImplicitSBR,faacDecFrameInfo.samplerate],,,[#include <faad.h>]) AC_DEFINE(HAVE_FAAD,1,[Define to use FAAD2 for AAC decoding]) else AC_MSG_WARN([faad2 lib needed for MP4/AAC support -- disabling MP4/AAC support]) @@ -145,7 +80,7 @@ int main() { unsigned char channels; uint32_t sample_rate; - faacDecInit2(NULL, NULL, 0, &sample_rate, &channels); + NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels); return 0; } ])], @@ -156,40 +91,9 @@ int main() { CFLAGS=$oldcflags LIBS=$oldlibs CPPFLAGS=$oldcppflags -fi - -if test x$enable_aac = xyes; then - enable_mp4=yes - MP4FF_LIBS="-lmp4ff" - - oldcflags=$CFLAGS - oldlibs=$LIBS - oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $FAAD_CFLAGS" - LIBS="$LIBS $FAAD_LIBS $MP4FF_LIBS" - CPPFLAGS=$CFLAGS - - AC_CHECK_HEADER(mp4ff.h,,enable_mp4=no) - - if test x$enable_mp4 = xyes; then - AC_CHECK_LIB(mp4ff,mp4ff_open_read,,enable_mp4=no) - fi - - if test x$enable_mp4 = xyes; then - AC_SUBST(MP4FF_LIBS) - AC_DEFINE(HAVE_MP4, 1, [Define to use FAAD2+mp4ff for MP4 decoding]) - else - AC_MSG_WARN([libmp4ff needed for MP4 support -- disabling MP4 support]) - unset MP4FF_LIBS - fi - - CFLAGS=$oldcflags - LIBS=$oldlibs - CPPFLAGS=$oldcppflags else - enable_mp4=no - FAAD_CFLAGS="" FAAD_LIBS="" + FAAD_CFLAGS="" fi AC_SUBST(FAAD_CFLAGS) diff --git a/m4/mpd_func.m4 b/m4/mpd_func.m4 new file mode 100644 index 000000000..d12d27062 --- /dev/null +++ b/m4/mpd_func.m4 @@ -0,0 +1,12 @@ +dnl MPD_OPTIONAL_FUNC(name, func, macro) +dnl +dnl Allow the user to enable or disable the use of a function. If the +dnl option is not specified, the function is auto-detected. +AC_DEFUN([MPD_OPTIONAL_FUNC], [ + AC_ARG_ENABLE([$1], + AS_HELP_STRING([--enable-$1], + [use the function "$1" (default: auto)]), + [test xenable_$1 = xyes && AC_DEFINE([$3], 1, [Define to use $1])], + [AC_CHECK_FUNC([$2], + [AC_DEFINE([$3], 1, [Define to use $1])],)]) +]) diff --git a/scripts/makedist.sh b/scripts/makedist.sh deleted file mode 100755 index 7f8624d8f..000000000 --- a/scripts/makedist.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -PWD=`pwd` - -## If we're not in the scripts directory -## assume the base directory. -if test "`basename $PWD`" = "scripts"; then - cd ../ -else - MYOLDPWD=`pwd` - cd `dirname $0`/../ -fi - -if test -e Makefile -then - make distclean -fi -./autogen.sh -make -make dist - -if test "`basename $PWD`" = "scripts"; then - cd contrib/ -else - cd $MYOLDPWD -fi diff --git a/scripts/mpd-indent.sh b/scripts/mpd-indent.sh deleted file mode 100755 index 0bf54189a..000000000 --- a/scripts/mpd-indent.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -indent -npro -kr -i8 -ts8 -sob -l80 -ss -ncs -cdw -cd0 -c0 -cp0 "$@" - -# there doesn't seem to be an indent switch for this, but this -# forces goto labels to the left-most column, without indentation -perl -i -p -e 's/^\s+(\w+):/$1:/g unless /^\s+default:/' "$@" diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index 739a8a6e7..000000000 --- a/scripts/test.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/sh -e -# -# This shell script tests the build of MPD with various compile-time -# options. -# -# Author: Max Kellermann <max@duempel.org> - -PREFIX=/tmp/mpd -rm -rf $PREFIX - -test "x$MAKE" != x || MAKE=make - -export CFLAGS="-Os" - -test -x configure || NOCONFIGURE=1 ./autogen.sh - -# all features on -./configure --prefix=$PREFIX/full \ - --disable-dependency-tracking --enable-debug --enable-werror \ - --enable-un \ - --enable-modplug \ - --enable-ao --enable-mikmod --enable-mvp -$MAKE install -$MAKE distclean - -# no UN, no oggvorbis, no flac, enable oggflac -./configure --prefix=$PREFIX/small \ - --disable-dependency-tracking --enable-debug --enable-werror \ - --disable-un \ - --disable-flac --disable-vorbis --enable-oggflac -$MAKE install -$MAKE distclean - -# strip down (disable TCP, disable nearly all plugins) -CFLAGS="$CFLAGS -DNDEBUG" \ -./configure --prefix=$PREFIX/tiny \ - --disable-dependency-tracking --disable-debug --enable-werror \ - --disable-tcp \ - --disable-curl \ - --disable-id3 --disable-lsr \ - --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \ - --disable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \ - --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \ - --disable-flac --disable-vorbis --disable-oggflac --disable-audiofile \ - --disable-cue \ - --with-zeroconf=no -$MAKE install -$MAKE distclean - -# shout: ogg without mp3 -# sndfile instead of modplug -./configure --prefix=$PREFIX/shout_ogg \ - --disable-dependency-tracking --disable-debug --enable-werror \ - --disable-tcp \ - --disable-curl \ - --disable-id3 --disable-lsr \ - --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \ - --enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \ - --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \ - --disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \ - --disable-modplug --enable-sndfile \ - --with-zeroconf=no -$MAKE install -$MAKE distclean - -# shout: mp3 without ogg -./configure --prefix=$PREFIX/shout_mp3 \ - --disable-dependency-tracking --disable-debug --enable-werror \ - --disable-tcp \ - --disable-curl \ - --disable-id3 --disable-lsr \ - --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \ - --disable-shout-ogg --enable-shout-mp3 --enable-lame-encoder \ - --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \ - --disable-flac --disable-vorbis --disable-oggflac --disable-audiofile \ - --with-zeroconf=no -$MAKE install -$MAKE distclean - -# oggvorbis + oggflac -./configure --prefix=$PREFIX/oggvorbisflac \ - --disable-dependency-tracking --disable-debug --enable-werror \ - --disable-tcp \ - --disable-curl \ - --disable-id3 --disable-lsr \ - --disable-mp3 \ - --disable-ao --disable-alsa --disable-jack --disable-pulse --disable-fifo \ - --disable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \ - --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \ - --disable-flac --enable-vorbis --enable-oggflac --disable-audiofile \ - --with-zeroconf=no -$MAKE install -$MAKE distclean diff --git a/src/AllCommands.cxx b/src/AllCommands.cxx new file mode 100644 index 000000000..e02e6df15 --- /dev/null +++ b/src/AllCommands.cxx @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AllCommands.hxx" +#include "command.h" +#include "QueueCommands.hxx" +#include "PlayerCommands.hxx" +#include "PlaylistCommands.hxx" +#include "DatabaseCommands.hxx" +#include "OutputCommands.hxx" +#include "MessageCommands.hxx" +#include "OtherCommands.hxx" +#include "Permission.hxx" +#include "tag/TagType.h" +#include "protocol/Result.hxx" +#include "Client.hxx" +#include "util/Tokenizer.hxx" +#include "util/Error.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerCommands.hxx" +#include "StickerDatabase.hxx" +#endif + +#include <assert.h> +#include <string.h> + +/* + * The most we ever use is for search/find, and that limits it to the + * number of tags we can have. Add one for the command, and one extra + * to catch errors clients may send us + */ +#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) + +/* if min: -1 don't check args * + * if max: -1 no max args */ +struct command { + const char *cmd; + unsigned permission; + int min; + int max; + enum command_return (*handler)(Client *client, int argc, char **argv); +}; + +/* don't be fooled, this is the command handler for "commands" command */ +static enum command_return +handle_commands(Client *client, int argc, char *argv[]); + +static enum command_return +handle_not_commands(Client *client, int argc, char *argv[]); + +/** + * The command registry. + * + * This array must be sorted! + */ +static const struct command commands[] = { + { "add", PERMISSION_ADD, 1, 1, handle_add }, + { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, + { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, + { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "close", PERMISSION_NONE, -1, -1, handle_close }, + { "commands", PERMISSION_NONE, 0, 0, handle_commands }, + { "config", PERMISSION_ADMIN, 0, 0, handle_config }, + { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, + { "count", PERMISSION_READ, 2, -1, handle_count }, + { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, + { "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 }, + { "listall", PERMISSION_READ, 0, 1, handle_listall }, + { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, + { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, + { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, + { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, + { "load", PERMISSION_ADD, 1, 2, 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 }, + { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, + { "outputs", PERMISSION_READ, 0, 0, handle_devices }, + { "password", PERMISSION_NONE, 1, 1, handle_password }, + { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, + { "ping", PERMISSION_NONE, 0, 0, handle_ping }, + { "play", PERMISSION_CONTROL, 0, 1, handle_play }, + { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, + { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, + { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, + { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, + { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, + { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, + { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, + { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, + { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, + { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, + { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, + { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, + { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, + { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, + { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, + { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, + { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, + handle_replay_gain_mode }, + { "replay_gain_status", PERMISSION_READ, 0, 0, + handle_replay_gain_status }, + { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, + { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, + { "save", PERMISSION_CONTROL, 1, 1, handle_save }, + { "search", PERMISSION_READ, 2, -1, handle_search }, + { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, + { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, + { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, + { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, + { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, + { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, + { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, + { "single", PERMISSION_CONTROL, 1, 1, handle_single }, + { "stats", PERMISSION_READ, 0, 0, handle_stats }, + { "status", PERMISSION_READ, 0, 0, handle_status }, +#ifdef ENABLE_SQLITE + { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, +#endif + { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, + { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, + { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, + { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, + { "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, + { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, +}; + +static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); + +static bool +command_available(gcc_unused const struct command *cmd) +{ +#ifdef ENABLE_SQLITE + if (strcmp(cmd->cmd, "sticker") == 0) + return sticker_enabled(); +#endif + + return true; +} + +/* don't be fooled, this is the command handler for "commands" command */ +static enum command_return +handle_commands(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + const unsigned permission = client_get_permission(client); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission == (permission & cmd->permission) && + command_available(cmd)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_not_commands(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + const unsigned permission = client_get_permission(client); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission != (permission & cmd->permission)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return COMMAND_RETURN_OK; +} + +void command_init(void) +{ +#ifndef NDEBUG + /* ensure that the command list is sorted */ + for (unsigned i = 0; i < num_commands - 1; ++i) + assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); +#endif +} + +void command_finish(void) +{ +} + +static const struct command * +command_lookup(const char *name) +{ + unsigned a = 0, b = num_commands, i; + int cmp; + + /* binary search */ + do { + i = (a + b) / 2; + + cmp = strcmp(name, commands[i].cmd); + if (cmp == 0) + return &commands[i]; + else if (cmp < 0) + b = i; + else if (cmp > 0) + a = i + 1; + } while (a < b); + + return NULL; +} + +static bool +command_check_request(const struct command *cmd, Client *client, + unsigned permission, int argc, char *argv[]) +{ + int min = cmd->min + 1; + int max = cmd->max + 1; + + if (cmd->permission != (permission & cmd->permission)) { + if (client != NULL) + command_error(client, ACK_ERROR_PERMISSION, + "you don't have permission for \"%s\"", + cmd->cmd); + return false; + } + + if (min == 0) + return true; + + if (min == max && max != argc) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "wrong number of arguments for \"%s\"", + argv[0]); + return false; + } else if (argc < min) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "too few arguments for \"%s\"", argv[0]); + return false; + } else if (argc > max && max /* != 0 */ ) { + if (client != NULL) + command_error(client, ACK_ERROR_ARG, + "too many arguments for \"%s\"", argv[0]); + return false; + } else + return true; +} + +static const struct command * +command_checked_lookup(Client *client, unsigned permission, + int argc, char *argv[]) +{ + const struct command *cmd; + + current_command = ""; + + if (argc == 0) + return NULL; + + cmd = command_lookup(argv[0]); + if (cmd == NULL) { + if (client != NULL) + command_error(client, ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", argv[0]); + return NULL; + } + + current_command = cmd->cmd; + + if (!command_check_request(cmd, client, permission, argc, argv)) + return NULL; + + return cmd; +} + +enum command_return +command_process(Client *client, unsigned num, char *line) +{ + Error error; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + const struct command *cmd; + enum command_return ret = COMMAND_RETURN_ERROR; + + command_list_num = num; + + /* get the command name (first word on the line) */ + + Tokenizer tokenizer(line); + argv[0] = tokenizer.NextWord(error); + if (argv[0] == NULL) { + current_command = ""; + if (tokenizer.IsEnd()) + command_error(client, ACK_ERROR_UNKNOWN, + "No command given"); + else + command_error(client, ACK_ERROR_UNKNOWN, + "%s", error.GetMessage()); + + current_command = NULL; + + return COMMAND_RETURN_ERROR; + } + + unsigned argc = 1; + + /* now parse the arguments (quoted or unquoted) */ + + while (argc < COMMAND_ARGV_MAX && + (argv[argc] = + tokenizer.NextParam(error)) != NULL) + ++argc; + + /* some error checks; we have to set current_command because + command_error() expects it to be set */ + + current_command = argv[0]; + + if (argc >= COMMAND_ARGV_MAX) { + command_error(client, ACK_ERROR_ARG, "Too many arguments"); + current_command = NULL; + return COMMAND_RETURN_ERROR; + } + + if (!tokenizer.IsEnd()) { + command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage()); + current_command = NULL; + 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/AllCommands.hxx b/src/AllCommands.hxx new file mode 100644 index 000000000..a55eb5a3b --- /dev/null +++ b/src/AllCommands.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ALL_COMMANDS_HXX +#define MPD_ALL_COMMANDS_HXX + +#include "command.h" + +class Client; + +void command_init(void); + +void command_finish(void); + +enum command_return +command_process(Client *client, unsigned num, char *line); + +#endif diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx new file mode 100644 index 000000000..de0ecc1e8 --- /dev/null +++ b/src/ArchiveFile.hxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FILE_HXX +#define MPD_ARCHIVE_FILE_HXX + +class Error; + +class ArchiveFile { +public: + const struct archive_plugin &plugin; + + ArchiveFile(const struct archive_plugin &_plugin) + :plugin(_plugin) {} + +protected: + /** + * Use Close() instead of delete. + */ + ~ArchiveFile() {} + +public: + virtual void Close() = 0; + + /** + * Visit all entries inside this archive. + */ + virtual void Visit(ArchiveVisitor &visitor) = 0; + + /** + * 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 occurring, or + * NULL to ignore errors + */ + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) = 0; +}; + +#endif diff --git a/src/ArchiveList.cxx b/src/ArchiveList.cxx new file mode 100644 index 000000000..894e31031 --- /dev/null +++ b/src/ArchiveList.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "util/StringUtil.hxx" +#include "archive/Bzip2ArchivePlugin.hxx" +#include "archive/Iso9660ArchivePlugin.hxx" +#include "archive/ZzipArchivePlugin.hxx" + +#include <string.h> +#include <glib.h> + +const struct archive_plugin *const archive_plugins[] = { +#ifdef HAVE_BZ2 + &bz2_archive_plugin, +#endif +#ifdef HAVE_ZZIP + &zzip_archive_plugin, +#endif +#ifdef HAVE_ISO9660 + &iso9660_archive_plugin, +#endif + NULL +}; + +/** which plugins have been initialized successfully? */ +static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1]; + +#define archive_plugins_for_each_enabled(plugin) \ + archive_plugins_for_each(plugin) \ + if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins]) + +const struct archive_plugin * +archive_plugin_from_suffix(const char *suffix) +{ + if (suffix == NULL) + return NULL; + + archive_plugins_for_each_enabled(plugin) + if (plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) + return plugin; + + return NULL; +} + +const struct archive_plugin * +archive_plugin_from_name(const char *name) +{ + archive_plugins_for_each_enabled(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} + +void archive_plugin_init_all(void) +{ + 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; + } +} + +void archive_plugin_deinit_all(void) +{ + archive_plugins_for_each_enabled(plugin) + if (plugin->finish != NULL) + plugin->finish(); +} + diff --git a/src/ArchiveList.hxx b/src/ArchiveList.hxx new file mode 100644 index 000000000..057c351de --- /dev/null +++ b/src/ArchiveList.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LIST_HXX +#define MPD_ARCHIVE_LIST_HXX + +struct archive_plugin; + +extern const struct archive_plugin *const archive_plugins[]; + +#define archive_plugins_for_each(plugin) \ + for (const struct archive_plugin *plugin, \ + *const*archive_plugin_iterator = &archive_plugins[0]; \ + (plugin = *archive_plugin_iterator) != NULL; \ + ++archive_plugin_iterator) + +/* interface for using plugins */ + +const struct archive_plugin * +archive_plugin_from_suffix(const char *suffix); + +const struct archive_plugin * +archive_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void archive_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void archive_plugin_deinit_all(void); + +#endif diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx new file mode 100644 index 000000000..c781fcdaf --- /dev/null +++ b/src/ArchiveLookup.cxx @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ArchiveLookup.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +static constexpr Domain archive_domain("archive"); + +/** + * + * archive_lookup is used to determine if part of pathname refers to an regular + * file (archive). If so then its also used to split pathname into archive file + * and path used to locate file in archive. It also returns suffix of the file. + * How it works: + * We do stat of the parent of input pathname as long as we find an regular file + * Normally this should never happen. When routine returns true pathname modified + * and split into archive, inpath and suffix. Otherwise nothing happens + * + * For example: + * + * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3 + * is split into archive: /music/path/Talco.zip + * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3 + * and suffix: zip + */ + +bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix) +{ + char *pathdupe; + int len, idx; + struct stat st_info; + bool ret = false; + + *archive = NULL; + *inpath = NULL; + *suffix = NULL; + + pathdupe = g_strdup(pathname); + len = idx = strlen(pathname); + + while (idx > 0) { + //try to stat if its real directory + if (stat(pathdupe, &st_info) == -1) { + if (errno != ENOTDIR) { + FormatErrno(archive_domain, + "Failed to stat %s", pathdupe); + break; + } + } else { + //is something found ins original path (is not an archive) + if (idx == len) { + break; + } + //its a file ? + if (S_ISREG(st_info.st_mode)) { + //so the upper should be file + pathname[idx] = 0; + ret = true; + *archive = pathname; + *inpath = pathname + idx+1; + + //try to get suffix + *suffix = NULL; + while (idx > 0) { + if (pathname[idx] == '.') { + *suffix = pathname + idx + 1; + break; + } + idx--; + } + break; + } else { + FormatError(archive_domain, + "Not a regular file: %s", + pathdupe); + break; + } + } + //find one dir up + while (idx > 0) { + if (pathdupe[idx] == '/') { + pathdupe[idx] = 0; + break; + } + idx--; + } + } + g_free(pathdupe); + return ret; +} + diff --git a/src/ArchiveLookup.hxx b/src/ArchiveLookup.hxx new file mode 100644 index 000000000..6e7669cb0 --- /dev/null +++ b/src/ArchiveLookup.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LOOKUP_HXX +#define MPD_ARCHIVE_LOOKUP_HXX + +/* + * This is the public API which is used by archive plugins to + * provide transparent archive decompression layer for mpd + * + */ + +bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); + +#endif + diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx new file mode 100644 index 000000000..dd2059c0f --- /dev/null +++ b/src/ArchivePlugin.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +ArchiveFile * +archive_file_open(const struct archive_plugin *plugin, const char *path, + Error &error) +{ + assert(plugin != NULL); + assert(plugin->open != NULL); + assert(path != NULL); + + ArchiveFile *file = plugin->open(path, error); + assert((file == nullptr) == error.IsDefined()); + + return file; +} diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx new file mode 100644 index 000000000..ac8c1c8e3 --- /dev/null +++ b/src/ArchivePlugin.hxx @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_ARCHIVE_PLUGIN_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct input_stream; +class ArchiveFile; +class ArchiveVisitor; +class Error; + +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 + */ + ArchiveFile *(*open)(const char *path_fs, Error &error); + + /** + * suffixes handled by this plugin. + * last element in these arrays must always be a NULL + */ + const char *const*suffixes; +}; + +ArchiveFile * +archive_file_open(const struct archive_plugin *plugin, const char *path, + Error &error); + +#endif diff --git a/src/ArchiveVisitor.hxx b/src/ArchiveVisitor.hxx new file mode 100644 index 000000000..e951cb5e9 --- /dev/null +++ b/src/ArchiveVisitor.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_VISITOR_HXX +#define MPD_ARCHIVE_VISITOR_HXX + +class ArchiveVisitor { +public: + virtual void VisitArchiveEntry(const char *path_utf8) = 0; +}; + +#endif diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h index 073d4af9a..8556d16be 100644 --- a/src/AudioCompress/compress.h +++ b/src/AudioCompress/compress.h @@ -19,6 +19,10 @@ struct CompressorConfig { struct Compressor; +#ifdef __cplusplus +extern "C" { +#endif + //! Create a new compressor (use history value of 0 for default) struct Compressor *Compressor_new(unsigned int history); @@ -34,7 +38,12 @@ struct CompressorConfig *Compressor_getConfig(struct Compressor *); //! Process 16-bit signed data void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count); +#ifdef __cplusplus +} +#endif + //! 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/AudioConfig.cxx b/src/AudioConfig.cxx new file mode 100644 index 000000000..aa09c374f --- /dev/null +++ b/src/AudioConfig.cxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AudioConfig.hxx" +#include "AudioFormat.hxx" +#include "AudioParser.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "util/Error.hxx" +#include "system/FatalError.hxx" + +static AudioFormat configured_audio_format; + +AudioFormat +getOutputAudioFormat(AudioFormat inAudioFormat) +{ + AudioFormat out_audio_format = inAudioFormat; + out_audio_format.ApplyMask(configured_audio_format); + return out_audio_format; +} + +void initAudioConfig(void) +{ + const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT); + + if (param == NULL) + return; + + Error error; + if (!audio_format_parse(configured_audio_format, param->value, + true, error)) + FormatFatalError("error parsing line %i: %s", + param->line, error.GetMessage()); +} diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx new file mode 100644 index 000000000..ebe202974 --- /dev/null +++ b/src/AudioConfig.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_HXX +#define MPD_AUDIO_CONFIG_HXX + +struct AudioFormat; + +AudioFormat +getOutputAudioFormat(AudioFormat inFormat); + +/* make sure initPlayerData is called before this function!! */ +void initAudioConfig(void); + +#endif diff --git a/src/AudioFormat.cxx b/src/AudioFormat.cxx new file mode 100644 index 000000000..04636c1e2 --- /dev/null +++ b/src/AudioFormat.cxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AudioFormat.hxx" + +#include <assert.h> +#include <stdio.h> + +void +AudioFormat::ApplyMask(AudioFormat mask) +{ + assert(IsValid()); + assert(mask.IsMaskValid()); + + if (mask.sample_rate != 0) + sample_rate = mask.sample_rate; + + if (mask.format != SampleFormat::UNDEFINED) + format = mask.format; + + if (mask.channels != 0) + channels = mask.channels; + + assert(IsValid()); +} + +const char * +sample_format_to_string(SampleFormat format) +{ + switch (format) { + case SampleFormat::UNDEFINED: + return "?"; + + case SampleFormat::S8: + return "8"; + + case SampleFormat::S16: + return "16"; + + case SampleFormat::S24_P32: + return "24"; + + case SampleFormat::S32: + return "32"; + + case SampleFormat::FLOAT: + return "f"; + + case SampleFormat::DSD: + return "dsd"; + } + + /* unreachable */ + assert(false); + gcc_unreachable(); +} + +const char * +audio_format_to_string(const AudioFormat af, + struct audio_format_string *s) +{ + assert(s != nullptr); + + snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u", + af.sample_rate, sample_format_to_string(af.format), + af.channels); + + return s->buffer; +} diff --git a/src/AudioFormat.hxx b/src/AudioFormat.hxx new file mode 100644 index 000000000..eb3f9b062 --- /dev/null +++ b/src/AudioFormat.hxx @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FORMAT_HXX +#define MPD_AUDIO_FORMAT_HXX + +#include "gcc.h" + +#include <stdint.h> +#include <assert.h> + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#endif + +enum class SampleFormat : uint8_t { + UNDEFINED = 0, + + S8, + S16, + + /** + * Signed 24 bit integer samples, packed in 32 bit integers + * (the most significant byte is filled with the sign bit). + */ + S24_P32, + + S32, + + /** + * 32 bit floating point samples in the host's format. The + * range is -1.0f to +1.0f. + */ + FLOAT, + + /** + * Direct Stream Digital. 1-bit samples; each frame has one + * byte (8 samples) per channel. + */ + DSD, +}; + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif + +static constexpr unsigned MAX_CHANNELS = 8; + +/** + * This structure describes the format of a raw PCM stream. + */ +struct AudioFormat { + /** + * 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; + + /** + * The format samples are stored in. See the #sample_format + * enum for valid values. + */ + SampleFormat format; + + /** + * The number of channels. Only mono (1) and stereo (2) are + * fully supported currently. + */ + uint8_t channels; + + AudioFormat() = default; + + constexpr AudioFormat(uint32_t _sample_rate, + SampleFormat _format, uint8_t _channels) + :sample_rate(_sample_rate), + format(_format), channels(_channels) {} + + static constexpr AudioFormat Undefined() { + return AudioFormat(0, SampleFormat::UNDEFINED,0); + } + + /** + * Clears the #audio_format object, i.e. sets all attributes to an + * undefined (invalid) value. + */ + void Clear() { + sample_rate = 0; + format = SampleFormat::UNDEFINED; + channels = 0; + } + + /** + * Checks whether the object has a defined value. + */ + constexpr bool IsDefined() const { + return sample_rate != 0; + } + + /** + * Checks whether the object is full, i.e. all attributes are + * defined. This is more complete than IsDefined(), but + * slower. + */ + constexpr bool IsFullyDefined() const { + return sample_rate != 0 && format != SampleFormat::UNDEFINED && + channels != 0; + } + + /** + * Checks whether the object has at least one defined value. + */ + constexpr bool IsMaskDefined() const { + return sample_rate != 0 || format != SampleFormat::UNDEFINED || + channels != 0; + } + + bool IsValid() const; + bool IsMaskValid() const; + + constexpr bool operator==(const AudioFormat other) const { + return sample_rate == other.sample_rate && + format == other.format && + channels == other.channels; + } + + constexpr bool operator!=(const AudioFormat other) const { + return !(*this == other); + } + + void ApplyMask(AudioFormat mask); + + /** + * Returns the size of each (mono) sample in bytes. + */ + unsigned GetSampleSize() const; + + /** + * Returns the size of each full frame in bytes. + */ + unsigned GetFrameSize() const; + + /** + * Returns the floating point factor which converts a time + * span to a storage size in bytes. + */ + double GetTimeToSize() const; +}; + +/** + * Buffer for audio_format_string(). + */ +struct audio_format_string { + char buffer[24]; +}; + +/** + * Checks whether the sample rate is valid. + * + * @param sample_rate the sample rate in Hz + */ +static constexpr inline bool +audio_valid_sample_rate(unsigned sample_rate) +{ + return sample_rate > 0 && sample_rate < (1 << 30); +} + +/** + * Checks whether the sample format is valid. + * + * @param bits the number of significant bits per sample + */ +static inline bool +audio_valid_sample_format(SampleFormat format) +{ + switch (format) { + case SampleFormat::S8: + case SampleFormat::S16: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + return true; + + case SampleFormat::UNDEFINED: + break; + } + + return false; +} + +/** + * Checks whether the number of channels is valid. + */ +static constexpr inline bool +audio_valid_channel_count(unsigned channels) +{ + return channels >= 1 && channels <= MAX_CHANNELS; +} + +/** + * Returns false if the format is not valid for playback with MPD. + * This function performs some basic validity checks. + */ +inline bool +AudioFormat::IsValid() const +{ + return audio_valid_sample_rate(sample_rate) && + audio_valid_sample_format(format) && + audio_valid_channel_count(channels); +} + +/** + * Returns false if the format mask is not valid for playback with + * MPD. This function performs some basic validity checks. + */ +inline bool +AudioFormat::IsMaskValid() const +{ + return (sample_rate == 0 || + audio_valid_sample_rate(sample_rate)) && + (format == SampleFormat::UNDEFINED || + audio_valid_sample_format(format)) && + (channels == 0 || audio_valid_channel_count(channels)); +} + +gcc_const +static inline unsigned +sample_format_size(SampleFormat format) +{ + switch (format) { + case SampleFormat::S8: + return 1; + + case SampleFormat::S16: + return 2; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + return 4; + + case SampleFormat::DSD: + /* each frame has 8 samples per channel */ + return 1; + + case SampleFormat::UNDEFINED: + return 0; + } + + assert(false); + gcc_unreachable(); +} + +inline unsigned +AudioFormat::GetSampleSize() const +{ + return sample_format_size(format); +} + +inline unsigned +AudioFormat::GetFrameSize() const +{ + return GetSampleSize() * channels; +} + +inline double +AudioFormat::GetTimeToSize() const +{ + return sample_rate * GetFrameSize(); +} + +/** + * 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 + */ +gcc_pure gcc_malloc +const char * +sample_format_to_string(SampleFormat 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 + */ +gcc_pure gcc_malloc +const char * +audio_format_to_string(AudioFormat af, + struct audio_format_string *s); + +#endif diff --git a/src/AudioParser.cxx b/src/AudioParser.cxx new file mode 100644 index 000000000..db6d1d8e8 --- /dev/null +++ b/src/AudioParser.cxx @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Parser functions for audio related objects. + * + */ + +#include "config.h" +#include "AudioParser.hxx" +#include "AudioFormat.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "gcc.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +static bool +parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r, + const char **endptr_r, Error &error) +{ + unsigned long value; + char *endptr; + + if (mask && *src == '*') { + *sample_rate_r = 0; + *endptr_r = src + 1; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + error.Set(audio_format_domain, + "Failed to parse the sample rate"); + return false; + } else if (!audio_check_sample_rate(value, error)) + return false; + + *sample_rate_r = value; + *endptr_r = endptr; + return true; +} + +static bool +parse_sample_format(const char *src, bool mask, + SampleFormat *sample_format_r, + const char **endptr_r, Error &error) +{ + unsigned long value; + char *endptr; + SampleFormat sample_format; + + if (mask && *src == '*') { + *sample_format_r = SampleFormat::UNDEFINED; + *endptr_r = src + 1; + return true; + } + + if (*src == 'f') { + *sample_format_r = SampleFormat::FLOAT; + *endptr_r = src + 1; + return true; + } + + if (memcmp(src, "dsd", 3) == 0) { + *sample_format_r = SampleFormat::DSD; + *endptr_r = src + 3; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + error.Set(audio_format_domain, + "Failed to parse the sample format"); + return false; + } + + switch (value) { + case 8: + sample_format = SampleFormat::S8; + break; + + case 16: + sample_format = SampleFormat::S16; + break; + + case 24: + if (memcmp(endptr, "_3", 2) == 0) + /* for backwards compatibility */ + endptr += 2; + + sample_format = SampleFormat::S24_P32; + break; + + case 32: + sample_format = SampleFormat::S32; + break; + + default: + error.Format(audio_format_domain, + "Invalid sample format: %lu", value); + return false; + } + + assert(audio_valid_sample_format(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, Error &error) +{ + unsigned long value; + char *endptr; + + if (mask && *src == '*') { + *channels_r = 0; + *endptr_r = src + 1; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + error.Set(audio_format_domain, + "Failed to parse the channel count"); + return false; + } else if (!audio_check_channel_count(value, error)) + return false; + + *channels_r = value; + *endptr_r = endptr; + return true; +} + +bool +audio_format_parse(AudioFormat &dest, const char *src, + bool mask, Error &error) +{ + uint32_t rate; + SampleFormat sample_format; + uint8_t channels; + + dest.Clear(); + + /* parse sample rate */ + +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + rate = 0; +#endif + + if (!parse_sample_rate(src, mask, &rate, &src, error)) + return false; + + if (*src++ != ':') { + error.Set(audio_format_domain, "Sample format missing"); + return false; + } + + /* parse sample format */ + +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + sample_format = SampleFormat::UNDEFINED; +#endif + + if (!parse_sample_format(src, mask, &sample_format, &src, error)) + return false; + + if (*src++ != ':') { + error.Set(audio_format_domain, "Channel count missing"); + return false; + } + + /* parse channel count */ + + if (!parse_channel_count(src, mask, &channels, &src, error)) + return false; + + if (*src != 0) { + error.Format(audio_format_domain, + "Extra data after channel count: %s", src); + return false; + } + + dest = AudioFormat(rate, sample_format, channels); + assert(mask + ? dest.IsMaskValid() + : dest.IsValid()); + + return true; +} diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx new file mode 100644 index 000000000..cb6eb3ca0 --- /dev/null +++ b/src/AudioParser.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * Parser functions for audio related objects. + */ + +#ifndef MPD_AUDIO_PARSER_HXX +#define MPD_AUDIO_PARSER_HXX + +struct AudioFormat; +class Error; + +/** + * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an + * #audio_format. + * + * @param dest the destination #audio_format struct + * @param src the input string + * @param mask if true, then "*" is allowed for any number of items + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ +bool +audio_format_parse(AudioFormat &dest, const char *src, + bool mask, Error &error); + +#endif diff --git a/src/CheckAudioFormat.cxx b/src/CheckAudioFormat.cxx new file mode 100644 index 000000000..1f3ef4925 --- /dev/null +++ b/src/CheckAudioFormat.cxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CheckAudioFormat.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> + +const Domain audio_format_domain("audio_format"); + +bool +audio_check_sample_rate(unsigned long sample_rate, Error &error) +{ + if (!audio_valid_sample_rate(sample_rate)) { + error.Format(audio_format_domain, + "Invalid sample rate: %lu", sample_rate); + return false; + } + + return true; +} + +bool +audio_check_sample_format(SampleFormat sample_format, Error &error) +{ + if (!audio_valid_sample_format(sample_format)) { + error.Format(audio_format_domain, + "Invalid sample format: %u", + unsigned(sample_format)); + return false; + } + + return true; +} + +bool +audio_check_channel_count(unsigned channels, Error &error) +{ + if (!audio_valid_channel_count(channels)) { + error.Format(audio_format_domain, + "Invalid channel count: %u", channels); + return false; + } + + return true; +} + +bool +audio_format_init_checked(AudioFormat &af, unsigned long sample_rate, + SampleFormat sample_format, unsigned channels, + Error &error) +{ + if (audio_check_sample_rate(sample_rate, error) && + audio_check_sample_format(sample_format, error) && + audio_check_channel_count(channels, error)) { + af = AudioFormat(sample_rate, sample_format, channels); + assert(af.IsValid()); + return true; + } else + return false; +} diff --git a/src/CheckAudioFormat.hxx b/src/CheckAudioFormat.hxx new file mode 100644 index 000000000..df952adc7 --- /dev/null +++ b/src/CheckAudioFormat.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_AUDIO_FORMAT_HXX +#define MPD_CHECK_AUDIO_FORMAT_HXX + +#include "AudioFormat.hxx" + +class Error; + +extern const class Domain audio_format_domain; + +bool +audio_check_sample_rate(unsigned long sample_rate, Error &error); + +bool +audio_check_sample_format(SampleFormat sample_format, Error &error); + +bool +audio_check_channel_count(unsigned sample_format, Error &error); + +/** + * Wrapper for audio_format_init(), which checks all attributes. + */ +bool +audio_format_init_checked(AudioFormat &af, unsigned long sample_rate, + SampleFormat sample_format, unsigned channels, + Error &error); + +#endif diff --git a/src/Client.cxx b/src/Client.cxx new file mode 100644 index 000000000..e7da3d1da --- /dev/null +++ b/src/Client.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "util/Domain.hxx" + +const Domain client_domain("client"); + +int client_get_uid(const Client *client) +{ + return client->uid; +} + +unsigned client_get_permission(const Client *client) +{ + return client->permission; +} + +void client_set_permission(Client *client, unsigned permission) +{ + client->permission = permission; +} diff --git a/src/Client.hxx b/src/Client.hxx new file mode 100644 index 000000000..1456f1b7d --- /dev/null +++ b/src/Client.hxx @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_H +#define MPD_CLIENT_H + +#include "gcc.h" + +#include <stddef.h> +#include <stdarg.h> + +struct sockaddr; +class EventLoop; +struct Partition; +class Client; + +void client_manager_init(void); + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid); + +/** + * returns the uid of the client process, or a negative value if the + * uid is unknown + */ +gcc_pure +int client_get_uid(const Client *client); + +/** + * Is this client running on the same machine, connected with a local + * (UNIX domain) socket? + */ +gcc_pure +static inline bool +client_is_local(const Client *client) +{ + return client_get_uid(client) > 0; +} + +gcc_pure +unsigned client_get_permission(const Client *client); + +void client_set_permission(Client *client, unsigned permission); + +/** + * Write a C string to the client. + */ +void client_puts(Client *client, const char *s); + +/** + * Write a printf-like formatted string to the client. + */ +void client_vprintf(Client *client, const char *fmt, va_list args); + +/** + * Write a printf-like formatted string to the client. + */ +gcc_fprintf +void +client_printf(Client *client, const char *fmt, ...); + +#endif diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx new file mode 100644 index 000000000..6c86057a1 --- /dev/null +++ b/src/ClientEvent.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +void +Client::OnSocketError(Error &&error) +{ + FormatError(error, "error on client %d", num); + + SetExpired(); +} + +void +Client::OnSocketClosed() +{ + SetExpired(); +} diff --git a/src/ClientExpire.cxx b/src/ClientExpire.cxx new file mode 100644 index 000000000..ea842cfd9 --- /dev/null +++ b/src/ClientExpire.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "Log.hxx" + +#include <glib.h> + +void +Client::SetExpired() +{ + if (IsExpired()) + return; + + FullyBufferedSocket::Close(); + TimeoutMonitor::Schedule(0); +} + +void +Client::OnTimeout() +{ + if (!IsExpired()) { + assert(!idle_waiting); + FormatDebug(client_domain, "[%u] timeout", num); + } + + Close(); +} diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx new file mode 100644 index 000000000..a460310be --- /dev/null +++ b/src/ClientFile.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientFile.hxx" +#include "Client.hxx" +#include "protocol/Ack.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> + +bool +client_allow_file(const Client *client, const Path &path_fs, + Error &error) +{ +#ifdef WIN32 + (void)client; + (void)path_fs; + + error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); + return false; +#else + const int uid = client_get_uid(client); + if (uid >= 0 && (uid_t)uid == geteuid()) + /* always allow access if user runs his own MPD + instance */ + return true; + + if (uid <= 0) { + /* unauthenticated client */ + error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); + return false; + } + + struct stat st; + if (!StatFile(path_fs, st)) { + error.SetErrno(); + return false; + } + + if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { + /* client is not owner */ + error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); + return false; + } + + return true; +#endif +} diff --git a/src/ClientFile.hxx b/src/ClientFile.hxx new file mode 100644 index 000000000..68d79ca08 --- /dev/null +++ b/src/ClientFile.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_FILE_HXX +#define MPD_CLIENT_FILE_HXX + +class Client; +class Path; +class Error; + +/** + * Is this client allowed to use the specified local file? + * + * Note that this function is vulnerable to timing/symlink attacks. + * We cannot fix this as long as there are plugins that open a file by + * its name, and not by file descriptor / callbacks. + * + * @param path_fs the absolute path name in filesystem encoding + * @return true if access is allowed + */ +bool +client_allow_file(const Client *client, const Path &path_fs, + Error &error); + +#endif diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx new file mode 100644 index 000000000..e79f3430b --- /dev/null +++ b/src/ClientGlobal.cxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "ClientList.hxx" +#include "ConfigGlobal.hxx" + +#define CLIENT_TIMEOUT_DEFAULT (60) +#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +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_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; +} diff --git a/src/ClientIdle.cxx b/src/ClientIdle.cxx new file mode 100644 index 000000000..714438123 --- /dev/null +++ b/src/ClientIdle.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> + +void +Client::IdleNotify() +{ + assert(idle_waiting); + assert(idle_flags != 0); + + unsigned flags = idle_flags; + idle_flags = 0; + idle_waiting = false; + + const char *const*idle_names = idle_get_names(); + for (unsigned i = 0; idle_names[i]; ++i) { + if (flags & (1 << i) & idle_subscriptions) + client_printf(this, "changed: %s\n", + idle_names[i]); + } + + client_puts(this, "OK\n"); + + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +Client::IdleAdd(unsigned flags) +{ + if (IsExpired()) + return; + + idle_flags |= flags; + if (idle_waiting && (idle_flags & idle_subscriptions)) + IdleNotify(); +} + +bool +Client::IdleWait(unsigned flags) +{ + assert(!idle_waiting); + + idle_waiting = true; + idle_subscriptions = flags; + + if (idle_flags & idle_subscriptions) { + IdleNotify(); + return true; + } else { + /* disable timeouts while in "idle" */ + TimeoutMonitor::Cancel(); + return false; + } +} diff --git a/src/ClientInternal.hxx b/src/ClientInternal.hxx new file mode 100644 index 000000000..e4c2525b4 --- /dev/null +++ b/src/ClientInternal.hxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_CLIENT_INTERNAL_HXX + +#include "check.h" +#include "Client.hxx" +#include "ClientMessage.hxx" +#include "CommandListBuilder.hxx" +#include "event/FullyBufferedSocket.hxx" +#include "event/TimeoutMonitor.hxx" +#include "command.h" + +#include <set> +#include <string> +#include <list> + +enum { + CLIENT_MAX_SUBSCRIPTIONS = 16, + CLIENT_MAX_MESSAGES = 64, +}; + +struct Partition; + +class Client final : private FullyBufferedSocket, TimeoutMonitor { +public: + Partition &partition; + struct playlist &playlist; + struct player_control *player_control; + + unsigned permission; + + /** the uid of the client process, or -1 if unknown */ + int uid; + + CommandListBuilder cmd_list; + + unsigned int num; /* client number */ + + /** 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; + + /** + * A list of channel names this client is subscribed to. + */ + std::set<std::string> subscriptions; + + /** + * The number of subscriptions in #subscriptions. Used to + * limit the number of subscriptions. + */ + unsigned num_subscriptions; + + /** + * A list of messages this client has received. + */ + std::list<ClientMessage> messages; + + Client(EventLoop &loop, Partition &partition, + int fd, int uid, int num); + + bool IsConnected() const { + return FullyBufferedSocket::IsDefined(); + } + + gcc_pure + bool IsSubscribed(const char *channel_name) const { + return subscriptions.find(channel_name) != subscriptions.end(); + } + + gcc_pure + bool IsExpired() const { + return !FullyBufferedSocket::IsDefined(); + } + + void Close(); + void SetExpired(); + + using FullyBufferedSocket::Write; + + /** + * Send "idle" response to this client. + */ + void IdleNotify(); + void IdleAdd(unsigned flags); + bool IdleWait(unsigned flags); + +private: + /* virtual methods from class BufferedSocket */ + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(Error &&error) override; + virtual void OnSocketClosed() override; + + /* virtual methods from class TimeoutMonitor */ + virtual void OnTimeout() override; +}; + +extern const class Domain client_domain; + +extern int client_timeout; +extern size_t client_max_command_list_size; +extern size_t client_max_output_buffer_size; + +enum command_return +client_process_line(Client *client, char *line); + +#endif diff --git a/src/ClientList.cxx b/src/ClientList.cxx new file mode 100644 index 000000000..37e6f1289 --- /dev/null +++ b/src/ClientList.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientList.hxx" +#include "ClientInternal.hxx" + +#include <algorithm> + +#include <assert.h> + +void +ClientList::Remove(Client &client) +{ + assert(size > 0); + assert(!list.empty()); + + auto i = std::find(list.begin(), list.end(), &client); + assert(i != list.end()); + list.erase(i); + --size; +} + +void +ClientList::CloseAll() +{ + while (!list.empty()) + list.front()->Close(); + + assert(size == 0); +} + +void +ClientList::IdleAdd(unsigned flags) +{ + assert(flags != 0); + + for (const auto &client : list) + client->IdleAdd(flags); +} diff --git a/src/ClientList.hxx b/src/ClientList.hxx new file mode 100644 index 000000000..e8560af78 --- /dev/null +++ b/src/ClientList.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LIST_HXX +#define MPD_CLIENT_LIST_HXX + +#include <list> + +class Client; + +class ClientList { + const unsigned max_size; + + unsigned size; + std::list<Client *> list; + +public: + ClientList(unsigned _max_size) + :max_size(_max_size), size(0) {} + + std::list<Client *>::iterator begin() { + return list.begin(); + } + + std::list<Client *>::iterator end() { + return list.end(); + } + + bool IsFull() const { + return size >= max_size; + } + + void Add(Client &client) { + list.push_front(&client); + ++size; + } + + void Remove(Client &client); + + void CloseAll(); + + void IdleAdd(unsigned flags); +}; + +#endif diff --git a/src/ClientMessage.cxx b/src/ClientMessage.cxx new file mode 100644 index 000000000..5a4ee6f01 --- /dev/null +++ b/src/ClientMessage.cxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientMessage.hxx" +#include "gcc.h" + +#include <glib.h> + +gcc_const +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} diff --git a/src/ClientMessage.hxx b/src/ClientMessage.hxx new file mode 100644 index 000000000..2a929d445 --- /dev/null +++ b/src/ClientMessage.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H + +#include "gcc.h" + +#include <string> + +/** + * A client-to-client message. + */ +class ClientMessage { + std::string channel, message; + +public: + template<typename T, typename U> + ClientMessage(T &&_channel, U &&_message) + :channel(std::forward<T>(_channel)), + message(std::forward<U>(_message)) {} + + const char *GetChannel() const { + return channel.c_str(); + } + + const char *GetMessage() const { + return message.c_str(); + } +}; + +gcc_pure +bool +client_message_valid_channel_name(const char *name); + +#endif diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx new file mode 100644 index 000000000..e7bb6e1c2 --- /dev/null +++ b/src/ClientNew.cxx @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "system/fd_util.h" +#include "system/Resolver.hxx" +#include "Permission.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#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 + +static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; + +Client::Client(EventLoop &_loop, Partition &_partition, + int _fd, int _uid, int _num) + :FullyBufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size), + TimeoutMonitor(_loop), + partition(_partition), + playlist(partition.playlist), player_control(&partition.pc), + permission(getDefaultPermissions()), + uid(_uid), + num(_num), + idle_waiting(false), idle_flags(0), + num_subscriptions(0) +{ + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid) +{ + static unsigned int next_client_num; + char *remote; + + assert(fd >= 0); + +#ifdef HAVE_LIBWRAP + if (sa->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(sa, sa_length, + IgnoreError()); + 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 */ + FormatWarning(client_domain, + "libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + + g_free(hostaddr); + close_socket(fd); + return; + } + + g_free(hostaddr); + } +#endif /* HAVE_WRAP */ + + ClientList &client_list = *partition.instance.client_list; + if (client_list.IsFull()) { + LogWarning(client_domain, "Max connections reached"); + close_socket(fd); + return; + } + + Client *client = new Client(loop, partition, fd, uid, + next_client_num++); + + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); + + client_list.Add(*client); + + remote = sockaddr_to_string(sa, sa_length, IgnoreError()); + FormatInfo(client_domain, "[%u] opened from %s", client->num, remote); + g_free(remote); +} + +void +Client::Close() +{ + partition.instance.client_list->Remove(*this); + + SetExpired(); + + FormatInfo(client_domain, "[%u] closed", num); + delete this; +} diff --git a/src/ClientProcess.cxx b/src/ClientProcess.cxx new file mode 100644 index 000000000..7da1664ca --- /dev/null +++ b/src/ClientProcess.cxx @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "AllCommands.hxx" +#include "Log.hxx" + +#include <glib.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(Client *client, bool list_ok, + std::list<std::string> &&list) +{ + enum command_return ret = COMMAND_RETURN_OK; + unsigned num = 0; + + for (auto &&i : list) { + char *cmd = &*i.begin(); + + FormatDebug(client_domain, "process command \"%s\"", cmd); + ret = command_process(client, num++, cmd); + FormatDebug(client_domain, "command returned %i", ret); + if (ret != COMMAND_RETURN_OK || client->IsExpired()) + break; + else if (list_ok) + client_puts(client, "list_OK\n"); + } + + return ret; +} + +enum command_return +client_process_line(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); + } + + /* 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" */ + FormatWarning(client_domain, + "[%u] command \"%s\" during idle", + client->num, line); + return COMMAND_RETURN_CLOSE; + } + + if (client->cmd_list.IsActive()) { + if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { + FormatDebug(client_domain, + "[%u] process command list", + client->num); + + auto &&cmd_list = client->cmd_list.Commit(); + + ret = client_process_command_list(client, + client->cmd_list.IsOKMode(), + std::move(cmd_list)); + FormatDebug(client_domain, + "[%u] process command " + "list returned %i", client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client->IsExpired()) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + + client->cmd_list.Reset(); + } else { + if (!client->cmd_list.Add(line)) { + FormatWarning(client_domain, + "[%u] command list size " + "is larger than the max (%lu)", + client->num, + (unsigned long)client_max_command_list_size); + return COMMAND_RETURN_CLOSE; + } + + ret = COMMAND_RETURN_OK; + } + } else { + if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { + client->cmd_list.Begin(false); + ret = COMMAND_RETURN_OK; + } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { + client->cmd_list.Begin(true); + ret = COMMAND_RETURN_OK; + } else { + FormatDebug(client_domain, + "[%u] process command \"%s\"", + client->num, line); + ret = command_process(client, 0, line); + FormatDebug(client_domain, + "[%u] command returned %i", + client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client->IsExpired()) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + } + } + + return ret; +} diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx new file mode 100644 index 000000000..8f7c4cf7e --- /dev/null +++ b/src/ClientRead.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +BufferedSocket::InputResult +Client::OnSocketInput(const void *data, size_t length) +{ + const char *p = (const char *)data; + const char *newline = (const char *)memchr(p, '\n', length); + if (newline == NULL) + return InputResult::MORE; + + TimeoutMonitor::ScheduleSeconds(client_timeout); + + char *line = g_strndup(p, newline - p); + BufferedSocket::ConsumeInput(newline + 1 - p); + + enum command_return result = client_process_line(this, line); + g_free(line); + + switch (result) { + case COMMAND_RETURN_OK: + case COMMAND_RETURN_IDLE: + case COMMAND_RETURN_ERROR: + break; + + case COMMAND_RETURN_KILL: + Close(); + main_loop->Break(); + return InputResult::CLOSED; + + case COMMAND_RETURN_CLOSE: + Close(); + return InputResult::CLOSED; + } + + if (IsExpired()) { + Close(); + return InputResult::CLOSED; + } + + return InputResult::AGAIN; +} diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx new file mode 100644 index 000000000..918a621db --- /dev/null +++ b/src/ClientSubscribe.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientSubscribe.hxx" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> +#include <string.h> + +enum client_subscribe_result +client_subscribe(Client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + auto r = client->subscriptions.insert(channel); + if (!r.second) + return CLIENT_SUBSCRIBE_ALREADY; + + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(Client *client, const char *channel) +{ + const auto i = client->subscriptions.find(channel); + if (i == client->subscriptions.end()) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions.erase(i); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + client->subscriptions.empty()); + + return true; +} + +void +client_unsubscribe_all(Client *client) +{ + client->subscriptions.clear(); + client->num_subscriptions = 0; +} + +bool +client_push_message(Client *client, const ClientMessage &msg) +{ + assert(client != NULL); + + if (client->messages.size() >= CLIENT_MAX_MESSAGES || + !client->IsSubscribed(msg.GetChannel())) + return false; + + if (client->messages.empty()) + client->IdleAdd(IDLE_MESSAGE); + + client->messages.push_back(msg); + return true; +} diff --git a/src/ClientSubscribe.hxx b/src/ClientSubscribe.hxx new file mode 100644 index 000000000..83c234db6 --- /dev/null +++ b/src/ClientSubscribe.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_SUBSCRIBE_HXX +#define MPD_CLIENT_SUBSCRIBE_HXX + +#include "gcc.h" + +class Client; +class ClientMessage; + +enum client_subscribe_result { + /** success */ + CLIENT_SUBSCRIBE_OK, + + /** invalid channel name */ + CLIENT_SUBSCRIBE_INVALID, + + /** already subscribed to this channel */ + CLIENT_SUBSCRIBE_ALREADY, + + /** too many subscriptions */ + CLIENT_SUBSCRIBE_FULL, +}; + +enum client_subscribe_result +client_subscribe(Client *client, const char *channel); + +bool +client_unsubscribe(Client *client, const char *channel); + +void +client_unsubscribe_all(Client *client); + +bool +client_push_message(Client *client, const ClientMessage &msg); + +#endif diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx new file mode 100644 index 000000000..dc18faafd --- /dev/null +++ b/src/ClientWrite.cxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ClientInternal.hxx" + +#include <glib.h> + +#include <string.h> +#include <stdio.h> + +/** + * Write a block of data to the client. + */ +static void +client_write(Client *client, const char *data, size_t length) +{ + /* if the client is going to be closed, do nothing */ + if (client->IsExpired() || length == 0) + return; + + client->Write(data, length); +} + +void +client_puts(Client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void +client_vprintf(Client *client, const char *fmt, va_list args) +{ +#ifndef G_OS_WIN32 + va_list tmp; + int length; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + char *buffer = (char *)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 +} + +void +client_printf(Client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/CommandError.cxx b/src/CommandError.cxx new file mode 100644 index 000000000..30c739128 --- /dev/null +++ b/src/CommandError.cxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandError.hxx" +#include "DatabaseError.hxx" +#include "protocol/Result.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <errno.h> + +enum command_return +print_playlist_result(Client *client, enum playlist_result result) +{ + switch (result) { + case PLAYLIST_RESULT_SUCCESS: + return COMMAND_RETURN_OK; + + case PLAYLIST_RESULT_ERRNO: + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DENIED: + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_SONG: + command_error(client, ACK_ERROR_NO_EXIST, "No such song"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NO_SUCH_LIST: + command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_LIST_EXISTS: + command_error(client, ACK_ERROR_EXIST, + "Playlist already exists"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_NAME: + command_error(client, ACK_ERROR_ARG, + "playlist name is invalid: " + "playlist names may not contain slashes," + " newlines or carriage returns"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_BAD_RANGE: + command_error(client, ACK_ERROR_ARG, "Bad song index"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_NOT_PLAYING: + command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_TOO_LARGE: + command_error(client, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return COMMAND_RETURN_ERROR; + + case PLAYLIST_RESULT_DISABLED: + command_error(client, ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); + return COMMAND_RETURN_ERROR; + } + + assert(0); + return COMMAND_RETURN_ERROR; +} + +enum command_return +print_error(Client *client, const Error &error) +{ + assert(client != NULL); + assert(error.IsDefined()); + + LogError(error); + + if (error.IsDomain(playlist_domain)) { + return print_playlist_result(client, + playlist_result(error.GetCode())); + } else if (error.IsDomain(ack_domain)) { + command_error(client, (ack)error.GetCode(), + "%s", error.GetMessage()); + return COMMAND_RETURN_ERROR; + } else if (error.IsDomain(db_domain)) { + switch ((enum db_error)error.GetCode()) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error.GetMessage()); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error.IsDomain(errno_domain)) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error.GetCode())); + return COMMAND_RETURN_ERROR; + } + + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} diff --git a/src/CommandError.hxx b/src/CommandError.hxx new file mode 100644 index 000000000..06f76390a --- /dev/null +++ b/src/CommandError.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_COMMAND_ERROR_HXX +#define MPD_COMMAND_ERROR_HXX + +#include "command.h" +#include "PlaylistError.hxx" + +class Client; +class Error; + +enum command_return +print_playlist_result(Client *client, enum playlist_result result); + +/** + * Send the #Error to the client. + */ +enum command_return +print_error(Client *client, const Error &error); + +#endif diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx new file mode 100644 index 000000000..85c929420 --- /dev/null +++ b/src/CommandLine.cxx @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CommandLine.hxx" +#include "ls.hxx" +#include "LogInit.hxx" +#include "Log.hxx" +#include "ConfigGlobal.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "OutputList.hxx" +#include "OutputPlugin.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" + +#ifdef ENABLE_ENCODER +#include "EncoderList.hxx" +#include "EncoderPlugin.hxx" +#endif + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#endif + +#include <glib.h> + +#include <stdio.h> +#include <stdlib.h> + +#ifdef 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" +#define USER_CONFIG_FILE_LOCATION_XDG "mpd/mpd.conf" +#endif + +static constexpr Domain cmdline_domain("cmdline"); + +gcc_noreturn +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-2012 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" + "Decoders plugins:"); + + decoder_plugins_for_each(plugin) { + printf(" [%s]", plugin->name); + + const char *const*suffixes = plugin->suffixes; + if (suffixes != NULL) + for (; *suffixes != NULL; ++suffixes) + printf(" %s", *suffixes); + + puts(""); + } + + puts("\n" + "Output plugins:"); + audio_output_plugins_for_each(plugin) + printf(" %s", plugin->name); + puts(""); + +#ifdef ENABLE_ENCODER + puts("\n" + "Encoder plugins:"); + encoder_plugins_for_each(plugin) + printf(" %s", plugin->name); + puts(""); +#endif + +#ifdef ENABLE_ARCHIVE + puts("\n" + "Archive plugins:"); + archive_plugins_for_each(plugin) { + printf(" [%s]", plugin->name); + + const char *const*suffixes = plugin->suffixes; + if (suffixes != NULL) + for (; *suffixes != NULL; ++suffixes) + printf(" %s", *suffixes); + + puts(""); + } +#endif + + puts("\n" + "Input plugins:"); + input_plugins_for_each(plugin) + printf(" %s", plugin->name); + + puts("\n\n" + "Playlist plugins:"); + playlist_plugins_for_each(plugin) + printf(" %s", plugin->name); + + puts("\n\n" + "Protocols:"); + print_supported_uri_schemes_to_fp(stdout); + + exit(EXIT_SUCCESS); +} + +static const char *summary = + "Music Player Daemon - a daemon for playing music."; + +gcc_pure +static Path +PathBuildChecked(const Path &a, Path::const_pointer b) +{ + if (a.IsNull()) + return Path::Null(); + + return Path::Build(a, b); +} + +bool +parse_cmdline(int argc, char **argv, struct options *options, + Error &error) +{ + GOptionContext *context; + bool ret; + static gboolean option_version, + option_no_daemon, + option_no_config; + const GOptionEntry entries[] = { + { "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-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, + "don't detach from console", NULL }, + { "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 }, + { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, + "print version number", NULL }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + options->kill = false; + options->daemon = true; + options->log_stderr = false; + options->verbose = false; + + context = g_option_context_new("[path/to/mpd.conf]"); + g_option_context_add_main_entries(context, entries, NULL); + + g_option_context_set_summary(context, summary); + + GError *gerror = nullptr; + ret = g_option_context_parse(context, &argc, &argv, &gerror); + g_option_context_free(context); + + if (!ret) + FatalError("option parsing failed", gerror); + + if (option_version) + version(); + + /* initialize the logging library, so the configuration file + parser can use it already */ + log_early_init(options->verbose); + + options->daemon = !option_no_daemon; + + if (option_no_config) { + LogDebug(cmdline_domain, + "Ignoring config, using daemon defaults"); + return true; + } else if (argc <= 1) { + /* default configuration file path */ + +#ifdef WIN32 + Path path = PathBuildChecked(Path::FromUTF8(g_get_user_config_dir()), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + const char *const*system_config_dirs = + g_get_system_config_dirs(); + + for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) { + path = PathBuildChecked(Path::FromUTF8(system_config_dirs[i]), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + } +#else /* G_OS_WIN32 */ + Path path = PathBuildChecked(Path::FromUTF8(g_get_user_config_dir()), + USER_CONFIG_FILE_LOCATION_XDG); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION1); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + path = PathBuildChecked(Path::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION2); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + path = Path::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); +#endif + + error.Set(cmdline_domain, "No configuration file found"); + return false; + } else if (argc == 2) { + /* specified configuration file */ + return ReadConfigFile(Path::FromFS(argv[1]), error); + } else { + error.Set(cmdline_domain, "too many arguments"); + return false; + } +} diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx new file mode 100644 index 000000000..214150eae --- /dev/null +++ b/src/CommandLine.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_COMMAND_LINE_HXX +#define MPD_COMMAND_LINE_HXX + +#include <glib.h> + +class Error; + +struct options { + gboolean kill; + gboolean daemon; + gboolean log_stderr; + gboolean verbose; +}; + +bool +parse_cmdline(int argc, char **argv, struct options *options, + Error &error); + +#endif diff --git a/src/CommandListBuilder.cxx b/src/CommandListBuilder.cxx new file mode 100644 index 000000000..cc10f7205 --- /dev/null +++ b/src/CommandListBuilder.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CommandListBuilder.hxx" +#include "ClientInternal.hxx" + +#include <string.h> + +void +CommandListBuilder::Reset() +{ + list.clear(); + mode = Mode::DISABLED; +} + +bool +CommandListBuilder::Add(const char *cmd) +{ + size_t len = strlen(cmd) + 1; + size += len; + if (size > client_max_command_list_size) + return false; + + list.emplace_back(cmd); + return true; +} diff --git a/src/CommandListBuilder.hxx b/src/CommandListBuilder.hxx new file mode 100644 index 000000000..a112ac33b --- /dev/null +++ b/src/CommandListBuilder.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_COMMAND_LIST_BUILDER_HXX +#define MPD_COMMAND_LIST_BUILDER_HXX + +#include <list> +#include <string> + +#include <assert.h> + +class CommandListBuilder { + /** + * print OK after each command execution + */ + enum class Mode { + /** + * Not active. + */ + DISABLED = -1, + + /** + * Enabled in normal list mode. + */ + ENABLED = false, + + /** + * Enabled in "list_OK" mode. + */ + OK = true, + } mode; + + /** + * for when in list mode + */ + std::list<std::string> list; + + /** + * Memory consumed by the list. + */ + size_t size; + +public: + CommandListBuilder() + :mode(Mode::DISABLED), size(0) {} + + /** + * Is a command list currently being built? + */ + bool IsActive() const { + return mode != Mode::DISABLED; + } + + /** + * Is the object in "list_OK" mode? + */ + bool IsOKMode() const { + assert(IsActive()); + + return (bool)mode; + } + + /** + * Reset the object: delete the list and clear the mode. + */ + void Reset(); + + /** + * Begin building a command list. + */ + void Begin(bool ok) { + assert(list.empty()); + assert(mode == Mode::DISABLED); + + mode = (Mode)ok; + } + + /** + * @return false if the list is full + */ + bool Add(const char *cmd); + + /** + * Finishes the list and returns it. + */ + std::list<std::string> &&Commit() { + assert(IsActive()); + + return std::move(list); + } +}; + +#endif diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx new file mode 100644 index 000000000..611c5b91a --- /dev/null +++ b/src/ConfigData.cxx @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigData.hxx" +#include "ConfigParser.hxx" +#include "ConfigPath.hxx" +#include "util/Error.hxx" +#include "fs/Path.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +unsigned +block_param::GetUnsignedValue() const +{ + char *endptr; + long value2 = strtol(value.c_str(), &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid number in line %i", line); + + if (value2 < 0) + FormatFatalError("Not a positive number in line %i", line); + + return (unsigned)value2; +} + +bool +block_param::GetBoolValue() const +{ + bool value2; + if (!get_bool(value.c_str(), &value2)) + FormatFatalError("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name.c_str(), line); + + return value2; +} + +config_param::config_param(const char *_value, int _line) + :next(nullptr), value(g_strdup(_value)), line(_line) {} + +config_param::~config_param() +{ + delete next; + g_free(value); +} + +const block_param * +config_param::GetBlockParam(const char *name) const +{ + for (const auto &i : block_params) { + if (i.name == name) { + i.used = true; + return &i; + } + } + + return NULL; +} + +const char * +config_param::GetBlockValue(const char *name, const char *default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == nullptr) + return default_value; + + return bp->value.c_str(); +} + +char * +config_param::DupBlockString(const char *name, const char *default_value) const +{ + return g_strdup(GetBlockValue(name, default_value)); +} + +Path +config_param::GetBlockPath(const char *name, const char *default_value, + Error &error) const +{ + assert(!error.IsDefined()); + + int line2 = line; + const char *s; + + const block_param *bp = GetBlockParam(name); + if (bp != nullptr) { + line2 = bp->line; + s = bp->value.c_str(); + } else { + if (default_value == nullptr) + return Path::Null(); + + s = default_value; + } + + Path path = ParsePath(s, error); + if (gcc_unlikely(path.IsNull())) + error.FormatPrefix("Invalid path in \"%s\" at line %i: ", + name, line2); + + return path; +} + +Path +config_param::GetBlockPath(const char *name, Error &error) const +{ + return GetBlockPath(name, nullptr, error); +} + +unsigned +config_param::GetBlockValue(const char *name, unsigned default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == nullptr) + return default_value; + + return bp->GetUnsignedValue(); +} + +gcc_pure +bool +config_param::GetBlockValue(const char *name, bool default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == NULL) + return default_value; + + return bp->GetBoolValue(); +} diff --git a/src/ConfigData.hxx b/src/ConfigData.hxx new file mode 100644 index 000000000..19613c610 --- /dev/null +++ b/src/ConfigData.hxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_DATA_HXX +#define MPD_CONFIG_DATA_HXX + +#include "ConfigOption.hxx" +#include "gcc.h" + +#include <string> +#include <array> +#include <vector> + +class Path; +class Error; + +struct block_param { + std::string name; + std::string value; + int line; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + mutable bool used; + + gcc_nonnull_all + block_param(const char *_name, const char *_value, int _line=-1) + :name(_name), value(_value), line(_line), used(false) {} + + gcc_pure + unsigned GetUnsignedValue() const; + + gcc_pure + bool GetBoolValue() const; +}; + +struct config_param { + /** + * The next config_param with the same name. The destructor + * deletes the whole chain. + */ + struct config_param *next; + + char *value; + unsigned int line; + + std::vector<block_param> block_params; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; + + config_param(int _line=-1) + :next(nullptr), value(nullptr), line(_line), used(false) {} + + gcc_nonnull_all + config_param(const char *_value, int _line=-1); + + config_param(const config_param &) = delete; + + ~config_param(); + + config_param &operator=(const config_param &) = delete; + + /** + * Determine if this is a "null" instance, i.e. an empty + * object that was synthesized and not loaded from a + * configuration file. + */ + bool IsNull() const { + return line == unsigned(-1); + } + + gcc_nonnull_all + void AddBlockParam(const char *_name, const char *_value, + int _line=-1) { + block_params.emplace_back(_name, _value, _line); + } + + gcc_nonnull_all gcc_pure + const block_param *GetBlockParam(const char *_name) const; + + gcc_pure + const char *GetBlockValue(const char *name, + const char *default_value=nullptr) const; + + gcc_malloc + char *DupBlockString(const char *name, + const char *default_value=nullptr) const; + + /** + * Same as config_dup_path(), but looks up the setting in the + * specified block. + */ + Path GetBlockPath(const char *name, const char *default_value, + Error &error) const; + + Path GetBlockPath(const char *name, Error &error) const; + + gcc_pure + unsigned GetBlockValue(const char *name, unsigned default_value) const; + + gcc_pure + bool GetBlockValue(const char *name, bool default_value) const; +}; + +struct ConfigData { + std::array<config_param *, std::size_t(CONF_MAX)> params; +}; + +#endif diff --git a/src/ConfigDefaults.hxx b/src/ConfigDefaults.hxx new file mode 100644 index 000000000..9cfe61582 --- /dev/null +++ b/src/ConfigDefaults.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_DEFAULTS_HXX +#define MPD_CONFIG_DEFAULTS_HXX + +static constexpr unsigned DEFAULT_PLAYLIST_MAX_LENGTH = 16 * 1024; +static constexpr bool DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS = false; + +#endif diff --git a/src/ConfigError.cxx b/src/ConfigError.cxx new file mode 100644 index 000000000..bd529e670 --- /dev/null +++ b/src/ConfigError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigError.hxx" +#include "util/Domain.hxx" + +const Domain config_domain("config"); diff --git a/src/ConfigError.hxx b/src/ConfigError.hxx new file mode 100644 index 000000000..a8917d7d1 --- /dev/null +++ b/src/ConfigError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_ERROR_HXX +#define MPD_CONFIG_ERROR_HXX + +extern const class Domain config_domain; + +#endif diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx new file mode 100644 index 000000000..7d2b4e7f7 --- /dev/null +++ b/src/ConfigFile.cxx @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigFile.hxx" +#include "ConfigError.hxx" +#include "ConfigData.hxx" +#include "ConfigTemplates.hxx" +#include "util/Tokenizer.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#define MAX_STRING_SIZE MPD_PATH_MAX+80 + +#define CONF_COMMENT '#' + +static constexpr Domain config_file_domain("config_file"); + +static bool +config_read_name_value(struct config_param *param, char *input, unsigned line, + Error &error) +{ + Tokenizer tokenizer(input); + + const char *name = tokenizer.NextWord(error); + if (name == NULL) { + assert(!tokenizer.IsEnd()); + return false; + } + + const char *value = tokenizer.NextString(error); + if (value == NULL) { + if (tokenizer.IsEnd()) { + error.Set(config_file_domain, "Value missing"); + } else { + assert(error.IsDefined()); + } + + return false; + } + + if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) { + error.Set(config_file_domain, "Unknown tokens after value"); + return false; + } + + const struct block_param *bp = param->GetBlockParam(name); + if (bp != NULL) { + error.Format(config_file_domain, + "\"%s\" is duplicate, first defined on line %i", + name, bp->line); + return false; + } + + param->AddBlockParam(name, value, line); + return true; +} + +static struct config_param * +config_read_block(FILE *fp, int *count, char *string, Error &error) +{ + struct config_param *ret = new config_param(*count); + + while (true) { + char *line; + + line = fgets(string, MAX_STRING_SIZE, fp); + if (line == NULL) { + delete ret; + error.Set(config_file_domain, + "Expected '}' before end-of-file"); + return NULL; + } + + (*count)++; + line = strchug_fast(line); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + if (*line == '}') { + /* end of this block; return from the function + (and from this "while" loop) */ + + line = strchug_fast(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + delete ret; + error.Format(config_file_domain, + "line %i: Unknown tokens after '}'", + *count); + return nullptr; + } + + return ret; + } + + /* parse name and value */ + + if (!config_read_name_value(ret, line, *count, error)) { + assert(*line != 0); + delete ret; + error.FormatPrefix("line %i: ", *count); + return NULL; + } + } +} + +gcc_nonnull_all +static void +Append(config_param *&head, config_param *p) +{ + assert(p->next == nullptr); + + config_param **i = &head; + while (*i != nullptr) + i = &(*i)->next; + + *i = p; +} + +static bool +ReadConfigFile(ConfigData &config_data, FILE *fp, Error &error) +{ + assert(fp != nullptr); + + char string[MAX_STRING_SIZE + 1]; + int count = 0; + struct config_param *param; + + while (fgets(string, MAX_STRING_SIZE, fp)) { + char *line; + const char *name, *value; + + count++; + + line = strchug_fast(string); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + /* the first token in each line is the name, followed + by either the value or '{' */ + + Tokenizer tokenizer(line); + name = tokenizer.NextWord(error); + if (name == NULL) { + assert(!tokenizer.IsEnd()); + error.FormatPrefix("line %i: ", count); + return false; + } + + /* get the definition of that option, and check the + "repeatable" flag */ + + const ConfigOption o = ParseConfigOptionName(name); + if (o == CONF_MAX) { + error.Format(config_file_domain, + "unrecognized parameter in config file at " + "line %i: %s\n", count, name); + return false; + } + + const unsigned i = unsigned(o); + const ConfigTemplate &option = config_templates[i]; + config_param *&head = config_data.params[i]; + + if (head != nullptr && !option.repeatable) { + param = head; + error.Format(config_file_domain, + "config parameter \"%s\" is first defined " + "on line %i and redefined on line %i\n", + name, param->line, count); + return false; + } + + /* now parse the block or the value */ + + if (option.block) { + /* it's a block, call config_read_block() */ + + if (tokenizer.CurrentChar() != '{') { + error.Format(config_file_domain, + "line %i: '{' expected", count); + return false; + } + + line = strchug_fast(tokenizer.Rest() + 1); + if (*line != 0 && *line != CONF_COMMENT) { + error.Format(config_file_domain, + "line %i: Unknown tokens after '{'", + count); + return false; + } + + param = config_read_block(fp, &count, string, error); + if (param == NULL) { + return false; + } + } else { + /* a string value */ + + value = tokenizer.NextString(error); + if (value == NULL) { + if (tokenizer.IsEnd()) + error.Format(config_file_domain, + "line %i: Value missing", + count); + else + error.FormatPrefix("line %i: ", count); + + return false; + } + + if (!tokenizer.IsEnd() && + tokenizer.CurrentChar() != CONF_COMMENT) { + error.Format(config_file_domain, + "line %i: Unknown tokens after value", + count); + return false; + } + + param = new config_param(value, count); + } + + Append(head, param); + } + + return true; +} + +bool +ReadConfigFile(ConfigData &config_data, const Path &path, Error &error) +{ + assert(!path.IsNull()); + const std::string path_utf8 = path.ToUTF8(); + + FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str()); + + FILE *fp = FOpen(path, FOpenMode::ReadText); + if (fp == nullptr) { + error.FormatErrno("Failed to open %s", path_utf8.c_str()); + return false; + } + + bool result = ReadConfigFile(config_data, fp, error); + fclose(fp); + return result; +} diff --git a/src/ConfigFile.hxx b/src/ConfigFile.hxx new file mode 100644 index 000000000..0dca65567 --- /dev/null +++ b/src/ConfigFile.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_FILE_HXX +#define MPD_CONFIG_FILE_HXX + +class Error; +class Path; +struct ConfigData; + +bool +ReadConfigFile(ConfigData &data, const Path &path, Error &error); + +#endif diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx new file mode 100644 index 000000000..b68a34a38 --- /dev/null +++ b/src/ConfigGlobal.cxx @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigGlobal.hxx" +#include "ConfigParser.hxx" +#include "ConfigData.hxx" +#include "ConfigFile.hxx" +#include "ConfigPath.hxx" +#include "ConfigError.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +static ConfigData config_data; + +void config_global_finish(void) +{ + for (auto i : config_data.params) + delete i; +} + +void config_global_init(void) +{ +} + +bool +ReadConfigFile(const Path &path, Error &error) +{ + return ReadConfigFile(config_data, path, error); +} + +static void +Check(const config_param *param) +{ + 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 (const auto &i : param->block_params) { + if (!i.used) + FormatWarning(config_domain, + "option '%s' on line %i was not recognized", + i.name.c_str(), i.line); + } +} + +void config_global_check(void) +{ + for (auto i : config_data.params) + for (const config_param *p = i; p != nullptr; p = p->next) + Check(p); +} + +const struct config_param * +config_get_next_param(ConfigOption option, const struct config_param * last) +{ + config_param *param = last != nullptr + ? last->next + : config_data.params[unsigned(option)]; + if (param != nullptr) + param->used = true; + return param; +} + +const char * +config_get_string(ConfigOption option, const char *default_value) +{ + const struct config_param *param = config_get_param(option); + + if (param == nullptr) + return default_value; + + return param->value; +} + +Path +config_get_path(ConfigOption option, Error &error) +{ + const struct config_param *param = config_get_param(option); + if (param == nullptr) + return Path::Null(); + + return config_parse_path(param, error); +} + +Path +config_parse_path(const struct config_param *param, Error & error) +{ + Path path = ParsePath(param->value, error); + if (gcc_unlikely(path.IsNull())) + error.FormatPrefix("Invalid path at line %i: ", + param->line); + + return path; +} + +unsigned +config_get_unsigned(ConfigOption option, unsigned default_value) +{ + const struct config_param *param = config_get_param(option); + long value; + char *endptr; + + if (param == nullptr) + return default_value; + + value = strtol(param->value, &endptr, 0); + if (*endptr != 0 || value < 0) + FormatFatalError("Not a valid non-negative number in line %i", + param->line); + + return (unsigned)value; +} + +unsigned +config_get_positive(ConfigOption option, unsigned default_value) +{ + const struct config_param *param = config_get_param(option); + long value; + char *endptr; + + if (param == nullptr) + return default_value; + + value = strtol(param->value, &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid number in line %i", param->line); + + if (value <= 0) + FormatFatalError("Not a positive number in line %i", + param->line); + + return (unsigned)value; +} + +bool +config_get_bool(ConfigOption option, bool default_value) +{ + const struct config_param *param = config_get_param(option); + bool success, value; + + if (param == nullptr) + return default_value; + + success = get_bool(param->value, &value); + if (!success) + FormatFatalError("Expected boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + param->line); + + return value; +} diff --git a/src/ConfigGlobal.hxx b/src/ConfigGlobal.hxx new file mode 100644 index 000000000..76b237153 --- /dev/null +++ b/src/ConfigGlobal.hxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_GLOBAL_HXX +#define MPD_CONFIG_GLOBAL_HXX + +#include "ConfigOption.hxx" +#include "gcc.h" + +class Error; +class Path; + +void config_global_init(void); +void config_global_finish(void); + +/** + * Call this function after all configuration has been evaluated. It + * checks for unused parameters, and logs warnings. + */ +void config_global_check(void); + +bool +ReadConfigFile(const Path &path, Error &error); + +/* don't free the returned value + set _last_ to nullptr to get first entry */ +gcc_pure +const struct config_param * +config_get_next_param(enum ConfigOption option, + const struct config_param *last); + +gcc_pure +static inline const struct config_param * +config_get_param(enum ConfigOption option) +{ + return config_get_next_param(option, nullptr); +} + +/* Note on gcc_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. */ + +gcc_pure +const char * +config_get_string(enum ConfigOption option, const char *default_value); + +/** + * Returns an optional configuration variable which contains an + * absolute path. If there is a tilde prefix, it is expanded. + * Returns Path::Null() if the value is not present. If the path + * could not be parsed, returns Path::Null() and sets the error. + */ +Path +config_get_path(enum ConfigOption option, Error &error); + +/** + * Parse a configuration parameter as a path. + * If there is a tilde prefix, it is expanded. If the path could + * not be parsed, returns Path::Null() and sets the error. + */ +Path +config_parse_path(const struct config_param *param, Error & error_r); + +gcc_pure +unsigned +config_get_unsigned(enum ConfigOption option, unsigned default_value); + +gcc_pure +unsigned +config_get_positive(enum ConfigOption option, unsigned default_value); + +gcc_pure +bool config_get_bool(enum ConfigOption option, bool default_value); + +#endif diff --git a/src/ConfigOption.hxx b/src/ConfigOption.hxx new file mode 100644 index 000000000..21a3a02e4 --- /dev/null +++ b/src/ConfigOption.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_OPTION_HXX +#define MPD_CONFIG_OPTION_HXX + +#include "gcc.h" + +enum ConfigOption { + CONF_MUSIC_DIR, + CONF_PLAYLIST_DIR, + CONF_FOLLOW_INSIDE_SYMLINKS, + CONF_FOLLOW_OUTSIDE_SYMLINKS, + CONF_DB_FILE, + CONF_STICKER_FILE, + CONF_LOG_FILE, + CONF_PID_FILE, + CONF_STATE_FILE, + CONF_RESTORE_PAUSED, + CONF_USER, + CONF_GROUP, + CONF_BIND_TO_ADDRESS, + CONF_PORT, + CONF_LOG_LEVEL, + CONF_ZEROCONF_NAME, + CONF_ZEROCONF_ENABLED, + CONF_PASSWORD, + CONF_DEFAULT_PERMS, + CONF_AUDIO_OUTPUT, + CONF_AUDIO_OUTPUT_FORMAT, + CONF_MIXER_TYPE, + CONF_REPLAYGAIN, + CONF_REPLAYGAIN_PREAMP, + CONF_REPLAYGAIN_MISSING_PREAMP, + CONF_REPLAYGAIN_LIMIT, + CONF_VOLUME_NORMALIZATION, + CONF_SAMPLERATE_CONVERTER, + CONF_AUDIO_BUFFER_SIZE, + CONF_BUFFER_BEFORE_PLAY, + CONF_HTTP_PROXY_HOST, + CONF_HTTP_PROXY_PORT, + CONF_HTTP_PROXY_USER, + CONF_HTTP_PROXY_PASSWORD, + CONF_CONN_TIMEOUT, + CONF_MAX_CONN, + CONF_MAX_PLAYLIST_LENGTH, + CONF_MAX_COMMAND_LIST_SIZE, + CONF_MAX_OUTPUT_BUFFER_SIZE, + CONF_FS_CHARSET, + CONF_ID3V1_ENCODING, + CONF_METADATA_TO_USE, + CONF_SAVE_ABSOLUTE_PATHS, + CONF_DECODER, + CONF_INPUT, + CONF_GAPLESS_MP3_PLAYBACK, + CONF_PLAYLIST_PLUGIN, + CONF_AUTO_UPDATE, + CONF_AUTO_UPDATE_DEPTH, + CONF_DESPOTIFY_USER, + CONF_DESPOTIFY_PASSWORD, + CONF_DESPOTIFY_HIGH_BITRATE, + CONF_AUDIO_FILTER, + CONF_DATABASE, + CONF_MAX +}; + +/** + * @return #CONF_MAX if not found + */ +gcc_pure +enum ConfigOption +ParseConfigOptionName(const char *name); + +#endif diff --git a/src/ConfigParser.cxx b/src/ConfigParser.cxx new file mode 100644 index 000000000..73381d8a0 --- /dev/null +++ b/src/ConfigParser.cxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigParser.hxx" +#include "util/StringUtil.hxx" + +bool +get_bool(const char *value, bool *value_r) +{ + static const char *t[] = { "yes", "true", "1", nullptr }; + static const char *f[] = { "no", "false", "0", nullptr }; + + if (string_array_contains(t, value)) { + *value_r = true; + return true; + } + + if (string_array_contains(f, value)) { + *value_r = false; + return true; + } + + return false; +} diff --git a/src/ConfigParser.hxx b/src/ConfigParser.hxx new file mode 100644 index 000000000..00fd42fe3 --- /dev/null +++ b/src/ConfigParser.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_PARSER_HXX +#define MPD_CONFIG_PARSER_HXX + +bool +get_bool(const char *value, bool *value_r); + +#endif diff --git a/src/ConfigPath.cxx b/src/ConfigPath.cxx new file mode 100644 index 000000000..7096bcc82 --- /dev/null +++ b/src/ConfigPath.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigPath.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "ConfigGlobal.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#ifndef WIN32 +#include <pwd.h> + +/** + * Determine a given user's home directory. + */ +static Path +GetHome(const char *user, Error &error) +{ + passwd *pw = getpwnam(user); + if (pw == nullptr) { + error.Format(path_domain, + "no such user: %s", user); + return Path::Null(); + } + + return Path::FromFS(pw->pw_dir); +} + +/** + * Determine the current user's home directory. + */ +static Path +GetHome(Error &error) +{ + const char *home = g_get_home_dir(); + if (home == nullptr) { + error.Set(path_domain, + "problems getting home for current user"); + return Path::Null(); + } + + return Path::FromUTF8(home, error); +} + +/** + * Determine the configured user's home directory. + */ +static Path +GetConfiguredHome(Error &error) +{ + const char *user = config_get_string(CONF_USER, nullptr); + return user != nullptr + ? GetHome(user, error) + : GetHome(error); +} + +#endif + +Path +ParsePath(const char *path, Error &error) +{ + assert(path != nullptr); + +#ifndef WIN32 + if (path[0] == '~') { + ++path; + + if (*path == '\0') + return GetConfiguredHome(error); + + Path home = Path::Null(); + + if (*path == '/') { + home = GetConfiguredHome(error); + + ++path; + } else { + const char *slash = strchr(path, '/'); + char *user = slash != nullptr + ? g_strndup(path, slash - path) + : g_strdup(path); + + home = GetHome(user, error); + g_free(user); + + if (slash == nullptr) + return home; + + path = slash + 1; + } + + if (home.IsNull()) + return Path::Null(); + + Path path2 = Path::FromUTF8(path, error); + if (path2.IsNull()) + return Path::Null(); + + return Path::Build(home, path2); + } else if (!g_path_is_absolute(path)) { + error.Format(path_domain, + "not an absolute path: %s", path); + return Path::Null(); + } else { +#endif + return Path::FromUTF8(path, error); +#ifndef WIN32 + } +#endif +} diff --git a/src/ConfigPath.hxx b/src/ConfigPath.hxx new file mode 100644 index 000000000..7bc1d732c --- /dev/null +++ b/src/ConfigPath.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_PATH_HXX +#define MPD_CONFIG_PATH_HXX + +class Path; +class Error; + +Path +ParsePath(const char *path, Error &error); + +#endif diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx new file mode 100644 index 000000000..6c6bf1689 --- /dev/null +++ b/src/ConfigTemplates.cxx @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigTemplates.hxx" +#include "ConfigOption.hxx" + +#include <string.h> + +const ConfigTemplate config_templates[] = { + { "music_directory", false, false }, + { "playlist_directory", false, false }, + { "follow_inside_symlinks", false, false }, + { "follow_outside_symlinks", false, false }, + { "db_file", false, false }, + { "sticker_file", false, false }, + { "log_file", false, false }, + { "pid_file", false, false }, + { "state_file", false, false }, + { "restore_paused", false, false }, + { "user", false, false }, + { "group", false, false }, + { "bind_to_address", true, false }, + { "port", false, false }, + { "log_level", false, false }, + { "zeroconf_name", false, false }, + { "zeroconf_enabled", false, false }, + { "password", true, false }, + { "default_permissions", false, false }, + { "audio_output", true, true }, + { "audio_output_format", false, false }, + { "mixer_type", false, false }, + { "replaygain", false, false }, + { "replaygain_preamp", false, false }, + { "replaygain_missing_preamp", false, false }, + { "replaygain_limit", false, false }, + { "volume_normalization", false, false }, + { "samplerate_converter", false, false }, + { "audio_buffer_size", false, false }, + { "buffer_before_play", false, false }, + { "http_proxy_host", false, false }, + { "http_proxy_port", false, false }, + { "http_proxy_user", false, false }, + { "http_proxy_password", false, false }, + { "connection_timeout", false, false }, + { "max_connections", false, false }, + { "max_playlist_length", false, false }, + { "max_command_list_size", false, false }, + { "max_output_buffer_size", false, false }, + { "filesystem_charset", false, false }, + { "id3v1_encoding", false, false }, + { "metadata_to_use", false, false }, + { "save_absolute_paths_in_playlists", false, false }, + { "decoder", true, true }, + { "input", true, true }, + { "gapless_mp3_playback", false, false }, + { "playlist_plugin", true, true }, + { "auto_update", false, false }, + { "auto_update_depth", false, false }, + { "despotify_user", false, false }, + { "despotify_password", false, false}, + { "despotify_high_bitrate", false, false }, + { "filter", true, true }, + { "database", false, true }, +}; + +static constexpr unsigned n_config_templates = + sizeof(config_templates) / sizeof(config_templates[0]); + +static_assert(n_config_templates == unsigned(CONF_MAX), + "Wrong number of config_templates"); + +ConfigOption +ParseConfigOptionName(const char *name) +{ + for (unsigned i = 0; i < n_config_templates; ++i) + if (strcmp(config_templates[i].name, name) == 0) + return ConfigOption(i); + + return CONF_MAX; +} diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx new file mode 100644 index 000000000..4f5460460 --- /dev/null +++ b/src/ConfigTemplates.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_TEMPLATES_HXX +#define MPD_CONFIG_TEMPLATES_HXX + +#include "ConfigOption.hxx" + +struct ConfigTemplate { + const char *const name; + const bool repeatable; + const bool block; +}; + +extern const ConfigTemplate config_templates[]; + +#endif diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx new file mode 100644 index 000000000..420276970 --- /dev/null +++ b/src/CrossFade.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CrossFade.hxx" +#include "MusicChunk.hxx" +#include "AudioFormat.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <cmath> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +static constexpr Domain cross_fade_domain("cross_fade"); + +#ifdef WIN32 + +static char * +strtok_r(char *str, const char *delim, gcc_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 (std::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 AudioFormat af, + const AudioFormat old_format, + unsigned max_chunks) +{ + unsigned int chunks = 0; + float chunks_f; + float mixramp_overlap; + + if (duration < 0 || duration >= total_time || + /* we can't crossfade when the audio formats are different */ + af != old_format) + return 0; + + assert(duration >= 0); + assert(af.IsValid()); + + chunks_f = (float)af.GetTimeToSize() / (float)CHUNK_SIZE; + + if (std::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 (!std::isnan(mixramp_overlap) && + mixramp_delay <= mixramp_overlap) { + chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); + FormatDebug(cross_fade_domain, + "will overlap %d chunks, %fs", chunks, + mixramp_overlap - mixramp_delay); + } + } + + if (chunks > max_chunks) { + chunks = max_chunks; + LogWarning(cross_fade_domain, + "audio_buffer_size too small for computed MixRamp overlap"); + } + + return chunks; +} diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx new file mode 100644 index 000000000..f285c0e9a --- /dev/null +++ b/src/CrossFade.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CROSSFADE_HXX +#define MPD_CROSSFADE_HXX + +struct AudioFormat; +struct music_chunk; + +/** + * Calculate how many music pipe chunks should be used for crossfading. + * + * @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 + * @return the number of chunks for crossfading, or 0 if cross fading + * 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, + AudioFormat af, AudioFormat old_format, + unsigned max_chunks); + +#endif diff --git a/src/Daemon.cxx b/src/Daemon.cxx new file mode 100644 index 000000000..8e22ed131 --- /dev/null +++ b/src/Daemon.cxx @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Daemon.hxx" +#include "system/FatalError.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#ifndef WIN32 +#include <signal.h> +#include <pwd.h> +#include <grp.h> +#endif + +static constexpr Domain daemon_domain("daemon"); + +#ifndef WIN32 + +/** the Unix user name which MPD runs as */ +static char *user_name; + +/** the Unix user id which MPD runs as */ +static uid_t user_uid = (uid_t)-1; + +/** the Unix group id which MPD runs as */ +static gid_t user_gid = (pid_t)-1; + +/** the absolute path of the pidfile */ +static Path pidfile = Path::Null(); + +/* whether "group" conf. option was given */ +static bool had_group = false; + + +void +daemonize_kill(void) +{ + FILE *fp; + int pid, ret; + + if (pidfile.IsNull()) + FatalError("no pid_file specified in the config file"); + + fp = FOpen(pidfile, "r"); + if (fp == nullptr) { + const std::string utf8 = pidfile.ToUTF8(); + FormatFatalSystemError("Unable to open pid file \"%s\"", + utf8.c_str()); + } + + if (fscanf(fp, "%i", &pid) != 1) { + const std::string utf8 = pidfile.ToUTF8(); + FormatFatalError("unable to read the pid from file \"%s\"", + utf8.c_str()); + } + fclose(fp); + + ret = kill(pid, SIGTERM); + if (ret < 0) + FormatFatalSystemError("unable to kill process %i", + int(pid)); + + exit(EXIT_SUCCESS); +} + +void +daemonize_close_stdin(void) +{ + close(STDIN_FILENO); + open("/dev/null", O_RDONLY); +} + +void +daemonize_set_user(void) +{ + if (user_name == nullptr) + return; + + /* set gid */ + if (user_gid != (gid_t)-1 && user_gid != getgid()) { + if (setgid(user_gid) == -1) { + FormatFatalSystemError("Failed to set group %d", + (int)user_gid); + } + } + +#ifdef _BSD_SOURCE + /* init suplementary groups + * (must be done before we change our uid) + */ + if (!had_group && initgroups(user_name, user_gid) == -1) { + FormatFatalSystemError("Failed to set supplementary groups " + "of user \"%s\"", + user_name); + } +#endif + + /* set uid */ + if (user_uid != (uid_t)-1 && user_uid != getuid() && + setuid(user_uid) == -1) { + FormatFatalSystemError("Failed to set user \"%s\"", + user_name); + } +} + +static void +daemonize_detach(void) +{ + /* flush all file handles before duplicating the buffers */ + + fflush(nullptr); + +#ifdef HAVE_DAEMON + + if (daemon(0, 1)) + FatalSystemError("daemon() failed"); + +#elif defined(HAVE_FORK) + + /* detach from parent process */ + + switch (fork()) { + case -1: + FatalSystemError("fork() failed"); + case 0: + break; + default: + /* exit the parent process */ + _exit(EXIT_SUCCESS); + } + + /* release the current working directory */ + + if (chdir("/") < 0) + FatalError("problems changing to root directory"); + + /* detach from the current session */ + + setsid(); + +#else + FatalError("no support for daemonizing"); +#endif + + LogDebug(daemon_domain, "daemonized"); +} + +void +daemonize(bool detach) +{ + FILE *fp = nullptr; + + if (!pidfile.IsNull()) { + /* do this before daemon'izing so we can fail gracefully if we can't + * write to the pid file */ + LogDebug(daemon_domain, "opening pid file"); + fp = FOpen(pidfile, "w+"); + if (!fp) { + const std::string utf8 = pidfile.ToUTF8(); + FormatFatalSystemError("Failed to create pid file \"%s\"", + pidfile.c_str()); + } + } + + if (detach) + daemonize_detach(); + + if (!pidfile.IsNull()) { + LogDebug(daemon_domain, "writing pid file"); + fprintf(fp, "%lu\n", (unsigned long)getpid()); + fclose(fp); + } +} + +void +daemonize_init(const char *user, const char *group, Path &&_pidfile) +{ + if (user) { + struct passwd *pwd = getpwnam(user); + if (pwd == nullptr) + FormatFatalError("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 = getgrnam(group); + if (grp == nullptr) + FormatFatalError("no such group \"%s\"", group); + user_gid = grp->gr_gid; + had_group = true; + } + + + pidfile = std::move(_pidfile); +} + +void +daemonize_finish(void) +{ + if (!pidfile.IsNull()) { + RemoveFile(pidfile); + pidfile = Path::Null(); + } + + g_free(user_name); +} + +#endif diff --git a/src/Daemon.hxx b/src/Daemon.hxx new file mode 100644 index 000000000..c662b9bee --- /dev/null +++ b/src/Daemon.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DAEMON_HXX +#define MPD_DAEMON_HXX + +class Path; + +#ifndef WIN32 +void +daemonize_init(const char *user, const char *group, Path &&pidfile); +#else +static inline void +daemonize_init(const char *user, const char *group, Path &&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 "system/FatalError.hxx" +static inline void +daemonize_kill(void) +{ + FatalError("--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/DatabaseCommands.cxx b/src/DatabaseCommands.cxx new file mode 100644 index 000000000..cc9e0937b --- /dev/null +++ b/src/DatabaseCommands.cxx @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DatabaseCommands.hxx" +#include "DatabaseQueue.hxx" +#include "DatabasePlaylist.hxx" +#include "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "CommandError.hxx" +#include "ClientInternal.hxx" +#include "tag/Tag.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "SongFilter.hxx" +#include "protocol/Result.hxx" + +#include <assert.h> +#include <string.h> + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + const DatabaseSelection selection(uri, false); + + Error error; + if (!db_selection_print(client, selection, true, error)) + return print_error(client, error); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_match(Client *client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + + Error error; + return db_selection_print(client, selection, true, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_find(Client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, false); +} + +enum command_return +handle_search(Client *client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, true); +} + +static enum command_return +handle_match_add(Client *client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + const DatabaseSelection selection("", true, &filter); + Error error; + return AddFromDatabase(client->partition, selection, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, false); +} + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, true); +} + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]) +{ + const char *playlist = argv[1]; + + SongFilter filter; + if (!filter.Parse(argc - 2, argv + 2, true)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + Error error; + return search_add_to_playlist("", playlist, &filter, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_count(Client *client, int argc, char *argv[]) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, false)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + Error error; + return searchStatsForSongsIn(client, "", &filter, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listall(Client *client, gcc_unused int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + Error error; + return printAllIn(client, directory, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_list(Client *client, int argc, char *argv[]) +{ + unsigned tagType = locate_parse_type(argv[1]); + + if (tagType == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return COMMAND_RETURN_ERROR; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + command_error(client, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return COMMAND_RETURN_ERROR; + } + + /* for compatibility with < 0.12.0 */ + SongFilter *filter; + if (argc == 3) { + if (tagType != TAG_ALBUM) { + command_error(client, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + tag_item_names[TAG_ALBUM]); + return COMMAND_RETURN_ERROR; + } + + filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); + } else if (argc > 2) { + filter = new SongFilter(); + if (!filter->Parse(argc - 2, argv + 2, false)) { + delete filter; + command_error(client, ACK_ERROR_ARG, + "not able to parse args"); + return COMMAND_RETURN_ERROR; + } + } else + filter = nullptr; + + Error error; + enum command_return ret = + listAllUniqueTags(client, tagType, filter, error) + ? COMMAND_RETURN_OK + : print_error(client, error); + + delete filter; + + return ret; +} + +enum command_return +handle_listallinfo(Client *client, gcc_unused int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + Error error; + return printInfoForAllIn(client, directory, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} diff --git a/src/DatabaseCommands.hxx b/src/DatabaseCommands.hxx new file mode 100644 index 000000000..335adc4d6 --- /dev/null +++ b/src/DatabaseCommands.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_COMMANDS_HXX +#define MPD_DATABASE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_lsinfo2(Client *client, int argc, char *argv[]); + +enum command_return +handle_find(Client *client, int argc, char *argv[]); + +enum command_return +handle_findadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_search(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_searchaddpl(Client *client, int argc, char *argv[]); + +enum command_return +handle_count(Client *client, int argc, char *argv[]); + +enum command_return +handle_listall(Client *client, int argc, char *argv[]); + +enum command_return +handle_list(Client *client, int argc, char *argv[]); + +enum command_return +handle_listallinfo(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/DatabaseError.cxx b/src/DatabaseError.cxx new file mode 100644 index 000000000..800442b9f --- /dev/null +++ b/src/DatabaseError.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DatabaseError.hxx" +#include "util/Domain.hxx" + +const Domain db_domain("db"); diff --git a/src/DatabaseError.hxx b/src/DatabaseError.hxx new file mode 100644 index 000000000..c133ee357 --- /dev/null +++ b/src/DatabaseError.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DB_ERROR_HXX +#define MPD_DB_ERROR_HXX + +class Domain; + +enum db_error { + /** + * The database is disabled, i.e. none is configured in this + * MPD instance. + */ + DB_DISABLED, + + DB_NOT_FOUND, +}; + +extern const Domain db_domain; + +#endif diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx new file mode 100644 index 000000000..d47108f71 --- /dev/null +++ b/src/DatabaseGlue.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "DatabaseRegistry.hxx" +#include "DatabaseSave.hxx" +#include "DatabaseError.hxx" +#include "Directory.hxx" +#include "util/Error.hxx" +#include "ConfigData.hxx" +#include "Stats.hxx" +#include "DatabasePlugin.hxx" +#include "db/SimpleDatabasePlugin.hxx" + +#include <glib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + + +static Database *db; +static bool db_is_open; +static bool is_simple; + +bool +DatabaseGlobalInit(const config_param ¶m, Error &error) +{ + assert(db == NULL); + assert(!db_is_open); + + const char *plugin_name = + param.GetBlockValue("plugin", "simple"); + is_simple = strcmp(plugin_name, "simple") == 0; + + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + error.Format(db_domain, + "No such database plugin: %s", plugin_name); + return false; + } + + db = plugin->create(param, error); + return db != NULL; +} + +void +DatabaseGlobalDeinit(void) +{ + if (db_is_open) + db->Close(); + + if (db != NULL) + delete db; +} + +const Database * +GetDatabase() +{ + assert(db == NULL || db_is_open); + + return db; +} + +const Database * +GetDatabase(Error &error) +{ + assert(db == nullptr || db_is_open); + + if (db == nullptr) + error.Set(db_domain, DB_DISABLED, "No database"); + + return db; +} + +bool +db_is_simple(void) +{ + assert(db == NULL || db_is_open); + + return is_simple; +} + +Directory * +db_get_root(void) +{ + assert(db != NULL); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->GetRoot(); +} + +Directory * +db_get_directory(const char *name) +{ + if (db == NULL) + return NULL; + + Directory *music_root = db_get_root(); + if (name == NULL) + return music_root; + + return music_root->LookupDirectory(name); +} + +bool +db_save(Error &error) +{ + assert(db != NULL); + assert(db_is_open); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->Save(error); +} + +bool +DatabaseGlobalOpen(Error &error) +{ + assert(db != NULL); + assert(!db_is_open); + + if (!db->Open(error)) + return false; + + db_is_open = true; + + stats_update(); + + return true; +} + +time_t +db_get_mtime(void) +{ + assert(db != NULL); + assert(db_is_open); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->GetLastModified(); +} diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx new file mode 100644 index 000000000..5e23a8c35 --- /dev/null +++ b/src/DatabaseGlue.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_GLUE_HXX +#define MPD_DATABASE_GLUE_HXX + +#include "gcc.h" + +struct config_param; +class Database; +class Error; + +/** + * Initialize the database library. + * + * @param param the database configuration block + */ +bool +DatabaseGlobalInit(const config_param ¶m, Error &error); + +void +DatabaseGlobalDeinit(void); + +bool +DatabaseGlobalOpen(Error &error); + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(); + +/** + * Returns the global #Database instance. May return NULL if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(Error &error); + +#endif diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx new file mode 100644 index 000000000..f250e447a --- /dev/null +++ b/src/DatabaseHelpers.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DatabaseHelpers.hxx" +#include "DatabasePlugin.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" + +#include <functional> +#include <set> + +#include <string.h> + +struct StringLess { + gcc_pure + bool operator()(const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +typedef std::set<const char *, StringLess> StringSet; + +static bool +CollectTags(StringSet &set, enum tag_type tag_type, Song &song) +{ + Tag *tag = song.tag; + if (tag == nullptr) + return true; + + bool found = false; + for (unsigned i = 0; i < tag->num_items; ++i) { + if (tag->items[i]->type == tag_type) { + set.insert(tag->items[i]->value); + found = true; + } + } + + if (!found) + set.insert(""); + + return true; +} + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error) +{ + StringSet set; + + using namespace std::placeholders; + const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1); + if (!db.Visit(selection, f, error)) + return false; + + for (auto value : set) + if (!visit_string(value, error)) + return false; + + return true; +} + +static void +StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, + const Tag &tag) +{ + if (tag.time > 0) + stats.total_duration += tag.time; + + for (unsigned i = 0; i < tag.num_items; ++i) { + const TagItem &item = *tag.items[i]; + + switch (item.type) { + case TAG_ARTIST: + artists.insert(item.value); + break; + + case TAG_ALBUM: + albums.insert(item.value); + break; + + default: + break; + } + } +} + +static bool +StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums, + Song &song) +{ + ++stats.song_count; + + if (song.tag != nullptr) + StatsVisitTag(stats, artists, albums, *song.tag); + + return true; +} + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) +{ + stats.Clear(); + + StringSet artists, albums; + using namespace std::placeholders; + const auto f = std::bind(StatsVisitSong, + std::ref(stats), std::ref(artists), + std::ref(albums), _1); + if (!db.Visit(selection, f, error)) + return false; + + stats.artist_count = artists.size(); + stats.album_count = albums.size(); + return true; +} diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx new file mode 100644 index 000000000..e666278f2 --- /dev/null +++ b/src/DatabaseHelpers.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MEMORY_DATABASE_PLUGIN_HXX +#define MPD_MEMORY_DATABASE_PLUGIN_HXX + +#include "DatabaseVisitor.hxx" +#include "tag/TagType.h" +#include "gcc.h" + +class Error; +class Database; +struct DatabaseSelection; +struct DatabaseStats; + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error); + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, Error &error); + +#endif diff --git a/src/DatabaseLock.cxx b/src/DatabaseLock.cxx new file mode 100644 index 000000000..de67012e7 --- /dev/null +++ b/src/DatabaseLock.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DatabaseLock.hxx" +#include "gcc.h" + +Mutex db_mutex; + +#ifndef NDEBUG +ThreadId db_mutex_holder; +#endif diff --git a/src/DatabaseLock.hxx b/src/DatabaseLock.hxx new file mode 100644 index 000000000..005835549 --- /dev/null +++ b/src/DatabaseLock.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Support for locking data structures from the database, for safe + * multi-threading. + */ + +#ifndef MPD_DB_LOCK_HXX +#define MPD_DB_LOCK_HXX + +#include "check.h" +#include "thread/Mutex.hxx" +#include "gcc.h" + +#include <assert.h> + +extern Mutex db_mutex; + +#ifndef NDEBUG + +#include "thread/Id.hxx" + +extern ThreadId db_mutex_holder; + +/** + * Does the current thread hold the database lock? + */ +gcc_pure +static inline bool +holding_db_lock(void) +{ + return db_mutex_holder.IsInside(); +} + +#endif + +/** + * Obtain the global database lock. This is needed before + * dereferencing a #song or #directory. It is not recursive. + */ +static inline void +db_lock(void) +{ + assert(!holding_db_lock()); + + db_mutex.lock(); + + assert(db_mutex_holder.IsNull()); +#ifndef NDEBUG + db_mutex_holder = ThreadId::GetCurrent(); +#endif +} + +/** + * Release the global database lock. + */ +static inline void +db_unlock(void) +{ + assert(holding_db_lock()); +#ifndef NDEBUG + db_mutex_holder = ThreadId::Null(); +#endif + + db_mutex.unlock(); +} + +class ScopeDatabaseLock { +public: + ScopeDatabaseLock() { + db_lock(); + } + + ~ScopeDatabaseLock() { + db_unlock(); + } +}; + +#endif diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx new file mode 100644 index 000000000..8404a39c9 --- /dev/null +++ b/src/DatabasePlaylist.cxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabasePlaylist.hxx" +#include "DatabaseSelection.hxx" +#include "PlaylistFile.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +AddSong(const char *playlist_path_utf8, + Song &song, Error &error) +{ + return spl_append_song(playlist_path_utf8, &song, error); +} + +bool +search_add_to_playlist(const char *uri, const char *playlist_path_utf8, + const SongFilter *filter, + Error &error) +{ + const Database *db = GetDatabase(error); + if (db == nullptr) + return false; + + const DatabaseSelection selection(uri, true, filter); + + using namespace std::placeholders; + const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2); + return db->Visit(selection, f, error); +} diff --git a/src/DatabasePlaylist.hxx b/src/DatabasePlaylist.hxx new file mode 100644 index 000000000..d91fe13be --- /dev/null +++ b/src/DatabasePlaylist.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX + +#include "gcc.h" + +class SongFilter; +class Error; + +gcc_nonnull(1,2) +bool +search_add_to_playlist(const char *uri, const char *path_utf8, + const SongFilter *filter, + Error &error); + +#endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx new file mode 100644 index 000000000..9bcbf49fd --- /dev/null +++ b/src/DatabasePlugin.hxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the db_plugin class. It describes a + * plugin API for databases of song metadata. + */ + +#ifndef MPD_DATABASE_PLUGIN_HXX +#define MPD_DATABASE_PLUGIN_HXX + +#include "DatabaseVisitor.hxx" +#include "tag/TagType.h" +#include "gcc.h" + +struct config_param; +struct DatabaseSelection; +struct db_visitor; +struct Song; +class Error; + +struct DatabaseStats { + /** + * Number of songs. + */ + unsigned song_count; + + /** + * Total duration of all songs (in seconds). + */ + unsigned long total_duration; + + /** + * Number of distinct artist names. + */ + unsigned artist_count; + + /** + * Number of distinct album names. + */ + unsigned album_count; + + void Clear() { + song_count = 0; + total_duration = 0; + artist_count = album_count = 0; + } +}; + +class Database { +public: + /** + * Free instance data. + */ + virtual ~Database() {} + + /** + * Open the database. Read it into memory if applicable. + */ + virtual bool Open(gcc_unused Error &error) { + return true; + } + + /** + * Close the database, free allocated memory. + */ + virtual void Close() {} + + /** + * Look up a song (including tag data) in the database. When + * you don't need this anymore, call ReturnSong(). + * + * @param uri_utf8 the URI of the song within the music + * directory (UTF-8) + */ + virtual Song *GetSong(const char *uri_utf8, + Error &error) const = 0; + + /** + * Mark the song object as "unused". Call this on objects + * returned by GetSong(). + */ + virtual void ReturnSong(Song *song) const = 0; + + /** + * Visit the selected entities. + */ + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const = 0; + + bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + Error &error) const { + return Visit(selection, visit_directory, visit_song, + VisitPlaylist(), error); + } + + bool Visit(const DatabaseSelection &selection, VisitSong visit_song, + Error &error) const { + return Visit(selection, VisitDirectory(), visit_song, error); + } + + /** + * Visit all unique tag values. + */ + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error) const = 0; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const = 0; +}; + +struct DatabasePlugin { + const char *name; + + /** + * Allocates and configures a database. + */ + Database *(*create)(const config_param ¶m, + Error &error); +}; + +#endif diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx new file mode 100644 index 000000000..c50c7686e --- /dev/null +++ b/src/DatabasePrint.cxx @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "SongFilter.hxx" +#include "PlaylistVector.hxx" +#include "SongPrint.hxx" +#include "TimePrint.hxx" +#include "Directory.hxx" +#include "Client.hxx" +#include "tag/Tag.hxx" +#include "Song.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +PrintDirectoryBrief(Client *client, const Directory &directory) +{ + if (!directory.IsRoot()) + client_printf(client, "directory: %s\n", directory.GetPath()); + + return true; +} + +static bool +PrintDirectoryFull(Client *client, const Directory &directory) +{ + if (!directory.IsRoot()) { + client_printf(client, "directory: %s\n", directory.GetPath()); + time_print(client, "Last-Modified", directory.mtime); + } + + return true; +} + + +static void +print_playlist_in_directory(Client *client, + const Directory &directory, + const char *name_utf8) +{ + if (directory.IsRoot()) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory.GetPath(), name_utf8); +} + +static bool +PrintSongBrief(Client *client, Song &song) +{ + assert(song.parent != NULL); + + song_print_uri(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintSongFull(Client *client, Song &song) +{ + assert(song.parent != NULL); + + song_print_info(client, &song); + + if (song.tag != NULL && song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, *song.parent, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + return true; +} + +static bool +PrintPlaylistFull(Client *client, + const PlaylistInfo &playlist, + const Directory &directory) +{ + print_playlist_in_directory(client, directory, playlist.name.c_str()); + + if (playlist.mtime > 0) + time_print(client, "Last-Modified", playlist.mtime); + + return true; +} + +bool +db_selection_print(Client *client, const DatabaseSelection &selection, + bool full, Error &error) +{ + const Database *db = GetDatabase(error); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto d = selection.filter == nullptr + ? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief, + client, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + client, _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + client, _1, _2) + : VisitPlaylist(); + + return db->Visit(selection, d, s, p, error); +} + +struct SearchStats { + int numberOfSongs; + unsigned long playTime; +}; + +static void printSearchStats(Client *client, SearchStats *stats) +{ + client_printf(client, "songs: %i\n", stats->numberOfSongs); + client_printf(client, "playtime: %li\n", stats->playTime); +} + +static bool +stats_visitor_song(SearchStats &stats, Song &song) +{ + stats.numberOfSongs++; + stats.playTime += song.GetDuration(); + + return true; +} + +bool +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, + Error &error) +{ + const Database *db = GetDatabase(error); + if (db == nullptr) + return false; + + const DatabaseSelection selection(name, true, filter); + + SearchStats stats; + stats.numberOfSongs = 0; + stats.playTime = 0; + + using namespace std::placeholders; + const auto f = std::bind(stats_visitor_song, std::ref(stats), + _1); + if (!db->Visit(selection, f, error)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(Client *client, const char *uri_utf8, Error &error) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, false, error); +} + +bool +printInfoForAllIn(Client *client, const char *uri_utf8, + Error &error) +{ + const DatabaseSelection selection(uri_utf8, true); + return db_selection_print(client, selection, true, error); +} + +static bool +PrintSongURIVisitor(Client *client, Song &song) +{ + song_print_uri(client, &song); + + return true; +} + +static bool +PrintUniqueTag(Client *client, enum tag_type tag_type, + const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); + return true; +} + +bool +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, + Error &error) +{ + const Database *db = GetDatabase(error); + if (db == nullptr) + return false; + + const DatabaseSelection selection("", true, filter); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, client, _1); + return db->Visit(selection, f, error); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, client, + (enum tag_type)type, _1); + return db->VisitUniqueTags(selection, (enum tag_type)type, + f, error); + } +} diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx new file mode 100644 index 000000000..042d0458e --- /dev/null +++ b/src/DatabasePrint.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DB_PRINT_H +#define MPD_DB_PRINT_H + +#include "gcc.h" + +class SongFilter; +struct DatabaseSelection; +struct db_visitor; +class Client; +class Error; + +gcc_nonnull(1) +bool +db_selection_print(Client *client, const DatabaseSelection &selection, + bool full, Error &error); + +gcc_nonnull(1,2) +bool +printAllIn(Client *client, const char *uri_utf8, Error &error); + +gcc_nonnull(1,2) +bool +printInfoForAllIn(Client *client, const char *uri_utf8, + Error &error); + +gcc_nonnull(1,2) +bool +searchStatsForSongsIn(Client *client, const char *name, + const SongFilter *filter, + Error &error); + +gcc_nonnull(1) +bool +listAllUniqueTags(Client *client, int type, + const SongFilter *filter, + Error &error); + +#endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx new file mode 100644 index 000000000..79ff004a7 --- /dev/null +++ b/src/DatabaseQueue.cxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseQueue.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Partition.hxx" +#include "util/Error.hxx" + +#include <functional> + +static bool +AddToQueue(Partition &partition, Song &song, Error &error) +{ + enum playlist_result result = + partition.playlist.AppendSong(partition.pc, &song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + error.Set(playlist_domain, result, "Playlist error"); + return false; + } + + return true; +} + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + Error &error) +{ + const Database *db = GetDatabase(error); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2); + return db->Visit(selection, f, error); +} diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx new file mode 100644 index 000000000..86e75a39b --- /dev/null +++ b/src/DatabaseQueue.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX + +struct Partition; +struct DatabaseSelection; +class Error; + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + Error &error); + +#endif diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx new file mode 100644 index 000000000..cf01decdd --- /dev/null +++ b/src/DatabaseRegistry.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseRegistry.hxx" +#include "db/SimpleDatabasePlugin.hxx" +#include "db/ProxyDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif + NULL +}; + +const DatabasePlugin * +GetDatabasePluginByName(const char *name) +{ + for (auto i = database_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx new file mode 100644 index 000000000..4be581573 --- /dev/null +++ b/src/DatabaseRegistry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_REGISTRY_HXX +#define MPD_DATABASE_REGISTRY_HXX + +#include "gcc.h" + +struct DatabasePlugin; + +/** + * NULL terminated list of all database plugins which were enabled at + * compile time. + */ +extern const DatabasePlugin *const database_plugins[]; + +gcc_pure +const DatabasePlugin * +GetDatabasePluginByName(const char *name); + +#endif diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx new file mode 100644 index 000000000..fb63d4969 --- /dev/null +++ b/src/DatabaseSave.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "DatabaseError.hxx" +#include "Directory.hxx" +#include "DirectorySave.hxx" +#include "Song.hxx" +#include "TextFile.hxx" +#include "tag/Tag.hxx" +#include "tag/TagSettings.h" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#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, +}; + +void +db_save_internal(FILE *fp, const Directory *music_root) +{ + assert(music_root != NULL); + + fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); + fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, + Path::GetFSCharset().c_str()); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (!ignore_tag_items[i]) + fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); + + fprintf(fp, "%s\n", DIRECTORY_INFO_END); + + directory_save(fp, music_root); +} + +bool +db_load_internal(TextFile &file, Directory *music_root, Error &error) +{ + char *line; + int format = 0; + bool found_charset = false, found_version = false; + bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; + + assert(music_root != NULL); + + /* get initial info */ + line = file.ReadLine(); + if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { + error.Set(db_domain, "Database corrupted"); + return false; + } + + memset(tags, false, sizeof(tags)); + + while ((line = file.ReadLine()) != 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) { + error.Set(db_domain, "Duplicate version line"); + return false; + } + + found_version = true; + } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { + const char *new_charset; + + if (found_charset) { + error.Set(db_domain, "Duplicate charset line"); + return false; + } + + found_charset = true; + + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; + const std::string &old_charset = Path::GetFSCharset(); + if (!old_charset.empty() + && strcmp(new_charset, old_charset.c_str())) { + error.Format(db_domain, + "Existing database has charset " + "\"%s\" instead of \"%s\"; " + "discarding database file", + new_charset, old_charset.c_str()); + 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) { + error.Format(db_domain, + "Unrecognized tag '%s', " + "discarding database file", + name); + return false; + } + + tags[tag] = true; + } else { + error.Format(db_domain, "Malformed line: %s", line); + return false; + } + } + + if (format != DB_FORMAT) { + error.Set(db_domain, + "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]) { + error.Set(db_domain, + "Tag list mismatch, " + "discarding database file"); + return false; + } + } + + LogDebug(db_domain, "reading DB"); + + db_lock(); + success = directory_load(file, music_root, error); + db_unlock(); + + return success; +} diff --git a/src/DatabaseSave.hxx b/src/DatabaseSave.hxx new file mode 100644 index 000000000..2410b0a71 --- /dev/null +++ b/src/DatabaseSave.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DATABASE_SAVE_HXX +#define MPD_DATABASE_SAVE_HXX + +#include <stdio.h> + +struct Directory; +class TextFile; +class Error; + +void +db_save_internal(FILE *file, const Directory *root); + +bool +db_load_internal(TextFile &file, Directory *root, Error &error); + +#endif diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx new file mode 100644 index 000000000..a372d5862 --- /dev/null +++ b/src/DatabaseSelection.cxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DatabaseSelection.hxx" +#include "SongFilter.hxx" + +bool +DatabaseSelection::Match(const Song &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx new file mode 100644 index 000000000..8ca04a3fc --- /dev/null +++ b/src/DatabaseSelection.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_SELECTION_HXX +#define MPD_DATABASE_SELECTION_HXX + +#include "gcc.h" + +#include <assert.h> +#include <stddef.h> + +class SongFilter; +struct Song; + +struct DatabaseSelection { + /** + * The base URI of the search (UTF-8). Must not begin or end + * with a slash. NULL or an empty string searches the whole + * database. + */ + const char *uri; + + /** + * Recursively search all sub directories? + */ + bool recursive; + + const SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr) + :uri(_uri), recursive(_recursive), filter(_filter) { + assert(uri != NULL); + } + + gcc_pure + bool Match(const Song &song) const; +}; + +#endif diff --git a/src/DatabaseSimple.hxx b/src/DatabaseSimple.hxx new file mode 100644 index 000000000..f0223bce2 --- /dev/null +++ b/src/DatabaseSimple.hxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DATABASE_SIMPLE_HXX +#define MPD_DATABASE_SIMPLE_HXX + +#include "gcc.h" + +#include <sys/time.h> + +struct config_param; +struct Directory; +struct db_selection; +struct db_visitor; +class Error; + +/** + * Check whether the default #SimpleDatabasePlugin is used. This + * allows using db_get_root(), db_save(), db_get_mtime() and + * db_exists(). + */ +bool +db_is_simple(void); + +/** + * Returns the root directory object. Returns NULL if there is no + * configured music directory. + * + * May only be used if db_is_simple() returns true. + */ +gcc_pure +Directory * +db_get_root(void); + +/** + * Caller must lock the #db_mutex. + */ +gcc_nonnull(1) +gcc_pure +Directory * +db_get_directory(const char *name); + +/** + * May only be used if db_is_simple() returns true. + */ +bool +db_save(Error &error); + +/** + * May only be used if db_is_simple() returns true. + */ +gcc_pure +time_t +db_get_mtime(void); + +/** + * Returns true if there is a valid database file on the disk. + * + * May only be used if db_is_simple() returns true. + */ +gcc_pure +static inline bool +db_exists(void) +{ + /* mtime is set only if the database file was loaded or saved + successfully */ + return db_get_mtime() > 0; +} + +#endif diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx new file mode 100644 index 000000000..cd6a7c94d --- /dev/null +++ b/src/DatabaseVisitor.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX + +#include <functional> + +struct Directory; +struct Song; +struct PlaylistInfo; +class Error; + +typedef std::function<bool(const Directory &, Error &)> VisitDirectory; +typedef std::function<bool(struct Song &, Error &)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const Directory &, + Error &)> VisitPlaylist; + +typedef std::function<bool(const char *, Error &)> VisitString; + +#endif diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx new file mode 100644 index 000000000..f7716a584 --- /dev/null +++ b/src/DecoderAPI.cxx @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderAPI.hxx" +#include "DecoderError.hxx" +#include "AudioConfig.hxx" +#include "ReplayGainConfig.hxx" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "Song.hxx" +#include "InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +void +decoder_initialized(struct decoder *decoder, + const AudioFormat audio_format, + bool seekable, float total_time) +{ + struct decoder_control *dc = decoder->dc; + struct audio_format_string af_string; + + assert(dc->state == DecoderState::START); + assert(dc->pipe != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(!decoder->seeking); + assert(audio_format.IsDefined()); + assert(audio_format.IsValid()); + + dc->in_audio_format = audio_format; + dc->out_audio_format = getOutputAudioFormat(audio_format); + + dc->seekable = seekable; + dc->total_time = total_time; + + dc->Lock(); + dc->state = DecoderState::DECODE; + dc->client_cond.signal(); + dc->Unlock(); + + FormatDebug(decoder_domain, "audio_format=%s, seekable=%s", + audio_format_to_string(dc->in_audio_format, &af_string), + seekable ? "true" : "false"); + + if (dc->in_audio_format != dc->out_audio_format) + FormatDebug(decoder_domain, "converting to %s", + audio_format_to_string(dc->out_audio_format, + &af_string)); +} + +/** + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. + */ +gcc_pure +static bool +decoder_prepare_initial_seek(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (dc->state != DecoderState::DECODE) + /* wait until the decoder has finished initialisation + (reading file headers etc.) before emitting the + virtual "SEEK" command */ + return false; + + if (decoder->initial_seek_running) + /* initial seek has already begun - override any other + command */ + return true; + + if (decoder->initial_seek_pending) { + if (!dc->seekable) { + /* seeking is not possible */ + decoder->initial_seek_pending = false; + return false; + } + + if (dc->command == DecoderCommand::NONE) { + /* begin initial seek */ + + decoder->initial_seek_pending = false; + decoder->initial_seek_running = true; + return true; + } + + /* skip initial seek when there's another command + (e.g. STOP) */ + + decoder->initial_seek_pending = false; + } + + return false; +} + +/** + * Returns the current decoder command. May return a "virtual" + * synthesized command, e.g. to seek to the beginning of the CUE + * track. + */ +gcc_pure +static DecoderCommand +decoder_get_virtual_command(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (decoder_prepare_initial_seek(decoder)) + return DecoderCommand::SEEK; + + return dc->command; +} + +DecoderCommand +decoder_get_command(struct decoder *decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void +decoder_command_finished(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + + dc->Lock(); + + assert(dc->command != DecoderCommand::NONE || + decoder->initial_seek_running); + assert(dc->command != DecoderCommand::SEEK || + decoder->initial_seek_running || + dc->seek_error || decoder->seeking); + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) { + assert(!decoder->seeking); + assert(decoder->chunk == NULL); + assert(dc->pipe->IsEmpty()); + + decoder->initial_seek_running = false; + decoder->timestamp = dc->start_ms / 1000.; + dc->Unlock(); + return; + } + + if (decoder->seeking) { + decoder->seeking = false; + + /* delete frames from the old song position */ + + if (decoder->chunk != NULL) { + dc->buffer->Return(decoder->chunk); + decoder->chunk = NULL; + } + + dc->pipe->Clear(*dc->buffer); + + decoder->timestamp = dc->seek_where; + } + + dc->command = DecoderCommand::NONE; + dc->client_cond.signal(); + dc->Unlock(); +} + +double decoder_seek_where(gcc_unused struct decoder * decoder) +{ + const struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) + return dc->start_ms / 1000.; + + assert(dc->command == DecoderCommand::SEEK); + + decoder->seeking = true; + + return dc->seek_where; +} + +void decoder_seek_error(struct decoder * decoder) +{ + struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) { + /* d'oh, we can't seek to the sub-song start position, + what now? - no idea, ignoring the problem for now. */ + decoder->initial_seek_running = false; + return; + } + + assert(dc->command == DecoderCommand::SEEK); + + dc->seek_error = true; + decoder->seeking = false; + + decoder_command_finished(decoder); +} + +/** + * Should be read operation be cancelled? That is the case when the + * player thread has sent a command such as "STOP". + */ +gcc_pure +static inline bool +decoder_check_cancel_read(const struct decoder *decoder) +{ + if (decoder == NULL) + return false; + + const struct decoder_control *dc = decoder->dc; + if (dc->command == DecoderCommand::NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc->command == DecoderCommand::SEEK && + (dc->state == DecoderState::START || decoder->seeking)) + return false; + + return true; +} + +size_t decoder_read(struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==NULL */ + + assert(decoder == NULL || + decoder->dc->state == DecoderState::START || + decoder->dc->state == DecoderState::DECODE); + assert(is != NULL); + assert(buffer != NULL); + + if (length == 0) + return 0; + + is->Lock(); + + while (true) { + if (decoder_check_cancel_read(decoder)) { + is->Unlock(); + return 0; + } + + if (is->IsAvailable()) + break; + + is->cond.wait(is->mutex); + } + + Error error; + size_t nbytes = is->Read(buffer, length, error); + assert(nbytes == 0 || !error.IsDefined()); + assert(nbytes > 0 || error.IsDefined() || is->IsEOF()); + + if (gcc_unlikely(nbytes == 0 && error.IsDefined())) + LogError(error); + + is->Unlock(); + + return nbytes; +} + +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. + */ +static DecoderCommand +do_send_tag(struct decoder *decoder, const Tag &tag) +{ + struct music_chunk *chunk; + + if (decoder->chunk != NULL) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder_flush_chunk(decoder); + decoder->dc->client_cond.signal(); + } + + assert(decoder->chunk == NULL); + + chunk = decoder_get_chunk(decoder); + if (chunk == NULL) { + assert(decoder->dc->command != DecoderCommand::NONE); + return decoder->dc->command; + } + + chunk->tag = new Tag(tag); + return DecoderCommand::NONE; +} + +static bool +update_stream_tag(struct decoder *decoder, struct input_stream *is) +{ + Tag *tag; + + tag = is != NULL + ? is->LockReadTag() + : NULL; + if (tag == NULL) { + tag = decoder->song_tag; + if (tag == NULL) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder->song_tag = NULL; + } + + delete decoder->stream_tag; + decoder->stream_tag = tag; + return true; +} + +DecoderCommand +decoder_data(struct decoder *decoder, + struct input_stream *is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + struct decoder_control *dc = decoder->dc; + DecoderCommand cmd; + + assert(dc->state == DecoderState::DECODE); + assert(dc->pipe != NULL); + assert(length % dc->in_audio_format.GetFrameSize() == 0); + + dc->Lock(); + cmd = decoder_get_virtual_command(decoder); + dc->Unlock(); + + if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK || + length == 0) + return cmd; + + /* send stream tags */ + + if (update_stream_tag(decoder, is)) { + if (decoder->decoder_tag != NULL) { + /* merge with tag from decoder plugin */ + Tag *tag = Tag::Merge(*decoder->decoder_tag, + *decoder->stream_tag); + cmd = do_send_tag(decoder, *tag); + delete tag; + } else + /* send only the stream tag */ + cmd = do_send_tag(decoder, *decoder->stream_tag); + + if (cmd != DecoderCommand::NONE) + return cmd; + } + + if (dc->in_audio_format != dc->out_audio_format) { + Error error; + data = decoder->conv_state.Convert(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 */ + LogError(error); + return DecoderCommand::STOP; + } + } + + while (length > 0) { + struct music_chunk *chunk; + size_t nbytes; + bool full; + + chunk = decoder_get_chunk(decoder); + if (chunk == NULL) { + assert(dc->command != DecoderCommand::NONE); + return dc->command; + } + + void *dest = chunk->Write(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); + dc->client_cond.signal(); + continue; + } + + assert(nbytes > 0); + + if (nbytes > length) + nbytes = length; + + /* copy the buffer */ + + memcpy(dest, data, nbytes); + + /* expand the music pipe chunk */ + + full = chunk->Expand(dc->out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder_flush_chunk(decoder); + dc->client_cond.signal(); + } + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + + decoder->timestamp += (double)nbytes / + dc->out_audio_format.GetTimeToSize(); + + if (dc->end_ms > 0 && + decoder->timestamp >= dc->end_ms / 1000.0) + /* the end of this range has been reached: + stop decoding */ + return DecoderCommand::STOP; + } + + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(gcc_unused struct decoder *decoder, struct input_stream *is, + Tag &&tag) +{ + gcc_unused const struct decoder_control *dc = decoder->dc; + DecoderCommand cmd; + + assert(dc->state == DecoderState::DECODE); + assert(dc->pipe != NULL); + + /* save the tag */ + + delete decoder->decoder_tag; + decoder->decoder_tag = new Tag(tag); + + /* check for a new stream tag */ + + update_stream_tag(decoder, is); + + /* check if we're seeking */ + + if (decoder_prepare_initial_seek(decoder)) + /* during initial seek, no music chunk must be created + until seeking is finished; skip the rest of the + function here */ + return DecoderCommand::SEEK; + + /* send tag to music pipe */ + + if (decoder->stream_tag != NULL) { + /* merge with tag from input stream */ + Tag *merged; + + merged = Tag::Merge(*decoder->stream_tag, + *decoder->decoder_tag); + cmd = do_send_tag(decoder, *merged); + delete merged; + } else + /* send only the decoder tag */ + cmd = do_send_tag(decoder, *decoder->decoder_tag); + + return cmd; +} + +void +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + assert(decoder != NULL); + + if (replay_gain_info != NULL) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + enum replay_gain_mode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + decoder->dc->replay_gain_db = 20.0 * log10f( + replay_gain_tuple_scale( + &replay_gain_info->tuples[rgm], + 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); + decoder->dc->client_cond.signal(); + } + } else + decoder->replay_gain_serial = 0; +} + +void +decoder_mixramp(struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + assert(decoder != NULL); + struct decoder_control *dc = decoder->dc; + assert(dc != NULL); + + dc->MixRampStart(mixramp_start); + dc->MixRampEnd(mixramp_end); +} diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx new file mode 100644 index 000000000..8bd38545f --- /dev/null +++ b/src/DecoderAPI.hxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 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_HXX +#define MPD_DECODER_API_HXX + +#include "check.h" +#include "DecoderCommand.hxx" +#include "DecoderPlugin.hxx" +#include "ReplayGainInfo.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "ConfigData.hxx" + +/** + * 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, + AudioFormat audio_format, + bool seekable, float total_time); + +/** + * Determines the pending decoder command. + * + * @param decoder the decoder object + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +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 DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week + */ +double +decoder_seek_where(struct decoder *decoder); + +/** + * Call this instead of decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +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); + +/** + * 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. + */ +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. + * + * @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 DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +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 DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_tag(struct decoder *decoder, struct input_stream *is, 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 + */ +void +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @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, + char *mixramp_start, char *mixramp_end); + +#endif diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx new file mode 100644 index 000000000..2125bbebd --- /dev/null +++ b/src/DecoderBuffer.cxx @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderBuffer.hxx" +#include "DecoderAPI.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct DecoderBuffer { + struct decoder *decoder; + struct input_stream *is; + + /** the allocated size of the buffer */ + size_t size; + + /** the current length of the buffer */ + size_t length; + + /** number of bytes already consumed at the beginning of the + buffer */ + size_t consumed; + + /** the actual buffer (dynamic size) */ + unsigned char data[sizeof(size_t)]; +}; + +DecoderBuffer * +decoder_buffer_new(struct decoder *decoder, struct input_stream *is, + size_t size) +{ + DecoderBuffer *buffer = (DecoderBuffer *) + g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); + + assert(is != nullptr); + assert(size > 0); + + buffer->decoder = decoder; + buffer->is = is; + buffer->size = size; + buffer->length = 0; + buffer->consumed = 0; + + return buffer; +} + +void +decoder_buffer_free(DecoderBuffer *buffer) +{ + assert(buffer != nullptr); + + g_free(buffer); +} + +bool +decoder_buffer_is_empty(const DecoderBuffer *buffer) +{ + return buffer->consumed == buffer->length; +} + +bool +decoder_buffer_is_full(const DecoderBuffer *buffer) +{ + return buffer->consumed == 0 && buffer->length == buffer->size; +} + +static void +decoder_buffer_shift(DecoderBuffer *buffer) +{ + assert(buffer->consumed > 0); + + buffer->length -= buffer->consumed; + memmove(buffer->data, buffer->data + buffer->consumed, buffer->length); + buffer->consumed = 0; +} + +bool +decoder_buffer_fill(DecoderBuffer *buffer) +{ + size_t nbytes; + + if (buffer->consumed > 0) + decoder_buffer_shift(buffer); + + if (buffer->length >= buffer->size) + /* buffer is full */ + return false; + + nbytes = decoder_read(buffer->decoder, buffer->is, + buffer->data + buffer->length, + buffer->size - buffer->length); + if (nbytes == 0) + /* end of file, I/O error or decoder command + received */ + return false; + + buffer->length += nbytes; + assert(buffer->length <= buffer->size); + + return true; +} + +const void * +decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r) +{ + if (buffer->consumed >= buffer->length) + /* buffer is empty */ + return nullptr; + + *length_r = buffer->length - buffer->consumed; + return buffer->data + buffer->consumed; +} + +void +decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes) +{ + /* just move the "consumed" pointer - decoder_buffer_shift() + will do the real work later (called by + decoder_buffer_fill()) */ + buffer->consumed += nbytes; + + assert(buffer->consumed <= buffer->length); +} + +bool +decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes) +{ + size_t length; + const void *data; + bool success; + + /* this could probably be optimized by seeking */ + + while (true) { + data = decoder_buffer_read(buffer, &length); + if (data != nullptr) { + if (length > nbytes) + length = nbytes; + decoder_buffer_consume(buffer, length); + nbytes -= length; + if (nbytes == 0) + return true; + } + + success = decoder_buffer_fill(buffer); + if (!success) + return false; + } +} diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx new file mode 100644 index 000000000..4f7efb29a --- /dev/null +++ b/src/DecoderBuffer.hxx @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_BUFFER_HXX +#define MPD_DECODER_BUFFER_HXX + +#include <stddef.h> + +/** + * This objects handles buffered reads in decoder plugins easily. You + * create a buffer object, and use its high-level methods to fill and + * read it. It will automatically handle shifting the buffer. + */ +struct DecoderBuffer; + +struct decoder; +struct input_stream; + +/** + * Creates a new buffer. + * + * @param decoder the decoder object, used for decoder_read(), may be NULL + * @param is the input stream object where we should read from + * @param size the maximum size of the buffer + * @return the new decoder_buffer object + */ +DecoderBuffer * +decoder_buffer_new(struct decoder *decoder, struct input_stream *is, + size_t size); + +/** + * Frees resources used by the decoder_buffer object. + */ +void +decoder_buffer_free(DecoderBuffer *buffer); + +bool +decoder_buffer_is_empty(const DecoderBuffer *buffer); + +bool +decoder_buffer_is_full(const DecoderBuffer *buffer); + +/** + * Read data from the input_stream and append it to the buffer. + * + * @return true if data was appended; false if there is no data + * available (yet), end of file, I/O error or a decoder command was + * received + */ +bool +decoder_buffer_fill(DecoderBuffer *buffer); + +/** + * Reads data from the buffer. This data is not yet consumed, you + * have to call decoder_buffer_consume() to do that. The returned + * buffer becomes invalid after a decoder_buffer_fill() or a + * decoder_buffer_consume() call. + * + * @param buffer the decoder_buffer object + * @param length_r pointer to a size_t where you will receive the + * number of bytes available + * @return a pointer to the read buffer, or NULL if there is no data + * available + */ +const void * +decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r); + +/** + * Consume (delete, invalidate) a part of the buffer. The "nbytes" + * parameter must not be larger than the length returned by + * decoder_buffer_read(). + * + * @param buffer the decoder_buffer object + * @param nbytes the number of bytes to consume + */ +void +decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes); + +/** + * Skips the specified number of bytes, discarding its data. + * + * @param buffer the decoder_buffer object + * @param nbytes the number of bytes to skip + * @return true on success, false on error + */ +bool +decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes); + +#endif diff --git a/src/DecoderCommand.hxx b/src/DecoderCommand.hxx new file mode 100644 index 000000000..394f270c2 --- /dev/null +++ b/src/DecoderCommand.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_COMMAND_HXX +#define MPD_DECODER_COMMAND_HXX + +#include <stdint.h> + +enum class DecoderCommand : uint8_t { + NONE = 0, + START, + STOP, + SEEK +}; + +#endif diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx new file mode 100644 index 000000000..07f6f9fde --- /dev/null +++ b/src/DecoderControl.cxx @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "Song.hxx" + +#include <glib.h> + +#include <assert.h> + +decoder_control::decoder_control() + :thread(nullptr), + state(DecoderState::STOP), + command(DecoderCommand::NONE), + song(nullptr), + replay_gain_db(0), replay_gain_prev_db(0), + mixramp_start(nullptr), mixramp_end(nullptr), + mixramp_prev_end(nullptr) {} + +decoder_control::~decoder_control() +{ + ClearError(); + + if (song != NULL) + song->Free(); + + g_free(mixramp_start); + g_free(mixramp_end); + g_free(mixramp_prev_end); +} + +bool +decoder_control::IsCurrentSong(const Song *_song) const +{ + assert(_song != NULL); + + switch (state) { + case DecoderState::STOP: + case DecoderState::ERROR: + return false; + + case DecoderState::START: + case DecoderState::DECODE: + return song_equals(song, _song); + } + + assert(false); + gcc_unreachable(); +} + +void +decoder_control::Start(Song *_song, + unsigned _start_ms, unsigned _end_ms, + MusicBuffer &_buffer, MusicPipe &_pipe) +{ + assert(_song != NULL); + assert(_pipe.IsEmpty()); + + if (song != nullptr) + song->Free(); + + song = _song; + start_ms = _start_ms; + end_ms = _end_ms; + buffer = &_buffer; + pipe = &_pipe; + + LockSynchronousCommand(DecoderCommand::START); +} + +void +decoder_control::Stop() +{ + Lock(); + + if (command != DecoderCommand::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). */ + SynchronousCommandLocked(DecoderCommand::STOP); + + if (state != DecoderState::STOP && state != DecoderState::ERROR) + SynchronousCommandLocked(DecoderCommand::STOP); + + Unlock(); +} + +bool +decoder_control::Seek(double where) +{ + assert(state != DecoderState::START); + assert(where >= 0.0); + + if (state == DecoderState::STOP || + state == DecoderState::ERROR || !seekable) + return false; + + seek_where = where; + seek_error = false; + LockSynchronousCommand(DecoderCommand::SEEK); + + return !seek_error; +} + +void +decoder_control::Quit() +{ + assert(thread != nullptr); + + quit = true; + LockAsynchronousCommand(DecoderCommand::STOP); + + g_thread_join(thread); + thread = nullptr; +} + +void +decoder_control::MixRampStart(char *_mixramp_start) +{ + g_free(mixramp_start); + mixramp_start = _mixramp_start; +} + +void +decoder_control::MixRampEnd(char *_mixramp_end) +{ + g_free(mixramp_end); + mixramp_end = _mixramp_end; +} + +void +decoder_control::MixRampPrevEnd(char *_mixramp_prev_end) +{ + g_free(mixramp_prev_end); + mixramp_prev_end = _mixramp_prev_end; +} diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx new file mode 100644 index 000000000..a787242e1 --- /dev/null +++ b/src/DecoderControl.hxx @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "DecoderCommand.hxx" +#include "AudioFormat.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stdint.h> + +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif + +struct Song; +class MusicBuffer; +class MusicPipe; +typedef struct _GThread GThread; + +enum class DecoderState : uint8_t { + STOP = 0, + START, + DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + ERROR, +}; + +struct decoder_control { + /** the handle of the decoder thread, or NULL if the decoder + thread isn't running */ + GThread *thread; + + /** + * This lock protects #state and #command. + */ + mutable Mutex 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. + */ + Cond cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + */ + Cond client_cond; + + DecoderState state; + DecoderCommand command; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DecoderState::ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DecoderState::START). + */ + Error error; + + bool quit; + bool seek_error; + bool seekable; + double seek_where; + + /** the format of the song file */ + AudioFormat in_audio_format; + + /** the format being sent to the music pipe */ + AudioFormat out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DecoderCommand::START + * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + Song *song; + + /** + * The initial seek position (in milliseconds), e.g. to the + * start of a sub-track described by a CUE file. + * + * This attribute is set by dc_start(). + */ + unsigned start_ms; + + /** + * The decoder will stop when it reaches this position (in + * milliseconds). 0 means don't stop before the end of the + * file. + * + * This attribute is set by dc_start(). + */ + unsigned end_ms; + + float total_time; + + /** the #music_chunk allocator */ + MusicBuffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + MusicPipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + char *mixramp_start; + char *mixramp_end; + char *mixramp_prev_end; + + decoder_control(); + ~decoder_control(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. This function is only valid in the + * player thread. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * 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. + */ + void Wait() { + cond.wait(mutex); + } + + /** + * Waits for a signal from the decoder thread. This object + * must be locked prior to calling this function. This method + * is only valid in the player thread. + */ + void WaitForDecoder() { + client_cond.wait(mutex); + } + + bool IsIdle() const { + return state == DecoderState::STOP || + state == DecoderState::ERROR; + } + + gcc_pure + bool LockIsIdle() const { + Lock(); + bool result = IsIdle(); + Unlock(); + return result; + } + + bool IsStarting() const { + return state == DecoderState::START; + } + + gcc_pure + bool LockIsStarting() const { + Lock(); + bool result = IsStarting(); + Unlock(); + return result; + } + + bool HasFailed() const { + assert(command == DecoderCommand::NONE); + + return state == DecoderState::ERROR; + } + + gcc_pure + bool LockHasFailed() const { + Lock(); + bool result = HasFailed(); + Unlock(); + return result; + } + + /** + * Checks whether an error has occurred, and if so, returns a + * copy of the #Error object. + * + * Caller must lock the object. + */ + gcc_pure + Error GetError() const { + assert(command == DecoderCommand::NONE); + assert(state != DecoderState::ERROR || error.IsDefined()); + + Error result; + if (state == DecoderState::ERROR) + result.Set(error); + return result; + } + + /** + * Like dc_get_error(), but locks and unlocks the object. + */ + gcc_pure + Error LockGetError() const { + Lock(); + Error result = GetError(); + Unlock(); + return result; + } + + /** + * Clear the error condition and free the #Error object (if any). + * + * Caller must lock the object. + */ + void ClearError() { + if (state == DecoderState::ERROR) { + error.Clear(); + state = DecoderState::STOP; + } + } + + /** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ + gcc_pure + bool IsCurrentSong(const Song *_song) const; + + gcc_pure + bool LockIsCurrentSong(const Song *_song) const { + Lock(); + const bool result = IsCurrentSong(_song); + Unlock(); + return result; + } + +private: + /** + * Wait for the command to be finished by the decoder thread. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void WaitCommandLocked() { + while (command != DecoderCommand::NONE) + WaitForDecoder(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void SynchronousCommandLocked(DecoderCommand cmd) { + command = cmd; + Signal(); + WaitCommandLocked(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. This method locks the + * object. + */ + void LockSynchronousCommand(DecoderCommand cmd) { + Lock(); + ClearError(); + SynchronousCommandLocked(cmd); + Unlock(); + } + + void LockAsynchronousCommand(DecoderCommand cmd) { + Lock(); + command = cmd; + Signal(); + Unlock(); + } + +public: + /** + * Start the decoder. + * + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_ms see #decoder_control + * @param end_ms see #decoder_control + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(Song *song, unsigned start_ms, unsigned end_ms, + MusicBuffer &buffer, MusicPipe &pipe); + + void Stop(); + + bool Seek(double where); + + void Quit(); + + void MixRampStart(char *_mixramp_start); + void MixRampEnd(char *_mixramp_end); + void MixRampPrevEnd(char *_mixramp_prev_end); +}; + +#endif diff --git a/src/DecoderError.cxx b/src/DecoderError.cxx new file mode 100644 index 000000000..a8d3f548d --- /dev/null +++ b/src/DecoderError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderError.hxx" +#include "util/Domain.hxx" + +const Domain decoder_domain("decoder"); diff --git a/src/DecoderError.hxx b/src/DecoderError.hxx new file mode 100644 index 000000000..9ea74167f --- /dev/null +++ b/src/DecoderError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ERROR_HXX +#define MPD_DECODER_ERROR_HXX + +extern const class Domain decoder_domain; + +#endif diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx new file mode 100644 index 000000000..307a3550b --- /dev/null +++ b/src/DecoderInternal.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderInternal.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> + +decoder::~decoder() +{ + /* caller must flush the chunk */ + assert(chunk == nullptr); + + delete song_tag; + delete stream_tag; + delete decoder_tag; +} + +/** + * All chunks are full of decoded data; wait for the player to free + * one. + */ +static DecoderCommand +need_chunks(struct decoder_control *dc, bool do_wait) +{ + if (dc->command == DecoderCommand::STOP || + dc->command == DecoderCommand::SEEK) + return dc->command; + + if (do_wait) { + dc->Wait(); + dc->client_cond.signal(); + + return dc->command; + } + + return DecoderCommand::NONE; +} + +struct music_chunk * +decoder_get_chunk(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + DecoderCommand cmd; + + assert(decoder != NULL); + + if (decoder->chunk != NULL) + return decoder->chunk; + + do { + decoder->chunk = dc->buffer->Allocate(); + 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; + } + + dc->Lock(); + cmd = need_chunks(dc, true); + dc->Unlock(); + } while (cmd == DecoderCommand::NONE); + + return NULL; +} + +void +decoder_flush_chunk(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + + assert(decoder != NULL); + assert(decoder->chunk != NULL); + + if (decoder->chunk->IsEmpty()) + dc->buffer->Return(decoder->chunk); + else + dc->pipe->Push(decoder->chunk); + + decoder->chunk = NULL; +} diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx new file mode 100644 index 000000000..5e0570c62 --- /dev/null +++ b/src/DecoderInternal.hxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX + +#include "DecoderCommand.hxx" +#include "pcm/PcmConvert.hxx" +#include "ReplayGainInfo.hxx" + +struct input_stream; +struct Tag; + +struct decoder { + struct decoder_control *dc; + + PcmConvert conv_state; + + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + + /** + * Is the initial seek (to the start position of the sub-song) + * pending, or has it been performed already? + */ + bool initial_seek_pending; + + /** + * Is the initial seek currently running? During this time, + * the decoder command is SEEK. This flag is set by + * decoder_get_virtual_command(), when the virtual SEEK + * command is generated for the first time. + */ + bool initial_seek_running; + + /** + * This flag is set by decoder_seek_where(), and checked by + * decoder_command_finished(). It is used to clean up after + * seeking. + */ + bool seeking; + + /** + * The tag from the song object. This is only used for local + * files, because we expect the stream server to send us a new + * tag each time we play it. + */ + Tag *song_tag; + + /** the last tag received from the stream */ + Tag *stream_tag; + + /** the last tag received from the decoder plugin */ + Tag *decoder_tag; + + /** 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; + + decoder(decoder_control *_dc, bool _initial_seek_pending, Tag *_tag) + :dc(_dc), + timestamp(0), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false), + seeking(false), + song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr), + chunk(nullptr), + replay_gain_serial(0) { + } + + ~decoder(); +}; + +/** + * Returns the current chunk the decoder writes to, or allocates a new + * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command + */ +struct music_chunk * +decoder_get_chunk(struct decoder *decoder); + +/** + * Flushes the current chunk. + */ +void +decoder_flush_chunk(struct decoder *decoder); + +#endif diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx new file mode 100644 index 000000000..fd80514dd --- /dev/null +++ b/src/DecoderList.cxx @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigData.hxx" +#include "decoder/AudiofileDecoderPlugin.hxx" +#include "decoder/PcmDecoderPlugin.hxx" +#include "decoder/DsdiffDecoderPlugin.hxx" +#include "decoder/DsfDecoderPlugin.hxx" +#include "decoder/FlacDecoderPlugin.h" +#include "decoder/OpusDecoderPlugin.h" +#include "decoder/VorbisDecoderPlugin.h" +#include "decoder/AdPlugDecoderPlugin.h" +#include "decoder/WavpackDecoderPlugin.hxx" +#include "decoder/FfmpegDecoderPlugin.hxx" +#include "decoder/GmeDecoderPlugin.hxx" +#include "decoder/FaadDecoderPlugin.hxx" +#include "decoder/MadDecoderPlugin.hxx" +#include "decoder/SndfileDecoderPlugin.hxx" +#include "decoder/Mpg123DecoderPlugin.hxx" +#include "decoder/WildmidiDecoderPlugin.hxx" +#include "decoder/MikmodDecoderPlugin.hxx" +#include "decoder/ModplugDecoderPlugin.hxx" +#include "decoder/MpcdecDecoderPlugin.hxx" +#include "decoder/FluidsynthDecoderPlugin.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <string.h> + +extern const struct decoder_plugin sidplay_decoder_plugin; + +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 +#if defined(HAVE_FLAC) + &oggflac_decoder_plugin, +#endif +#ifdef HAVE_FLAC + &flac_decoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif +#ifdef HAVE_AUDIOFILE + &audiofile_decoder_plugin, +#endif + &dsdiff_decoder_plugin, + &dsf_decoder_plugin, +#ifdef HAVE_FAAD + &faad_decoder_plugin, +#endif +#ifdef HAVE_MPCDEC + &mpcdec_decoder_plugin, +#endif +#ifdef HAVE_WAVPACK + &wavpack_decoder_plugin, +#endif +#ifdef HAVE_MODPLUG + &modplug_decoder_plugin, +#endif +#ifdef ENABLE_MIKMOD_DECODER + &mikmod_decoder_plugin, +#endif +#ifdef ENABLE_SIDPLAY + &sidplay_decoder_plugin, +#endif +#ifdef ENABLE_WILDMIDI + &wildmidi_decoder_plugin, +#endif +#ifdef ENABLE_FLUIDSYNTH + &fluidsynth_decoder_plugin, +#endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif +#ifdef HAVE_FFMPEG + &ffmpeg_decoder_plugin, +#endif +#ifdef HAVE_GME + &gme_decoder_plugin, +#endif + &pcm_decoder_plugin, + NULL +}; + +enum { + num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, +}; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +static unsigned +decoder_plugin_index(const struct decoder_plugin *plugin) +{ + 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; + + for (unsigned i = decoder_plugin_next_index(plugin); + decoder_plugins[i] != NULL; ++i) { + plugin = decoder_plugins[i]; + if (decoder_plugins_enabled[i] && + decoder_plugin_supports_suffix(plugin, suffix)) + return plugin; + } + + return NULL; +} + +const struct decoder_plugin * +decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) +{ + static unsigned i = num_decoder_plugins; + + if (mimeType == NULL) + return NULL; + + if (!next) + i = 0; + for (; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + if (decoder_plugins_enabled[i] && + decoder_plugin_supports_mime_type(plugin, mimeType)) { + ++i; + return plugin; + } + } + + return NULL; +} + +const struct decoder_plugin * +decoder_plugin_from_name(const char *name) +{ + decoder_plugins_for_each_enabled(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} + +/** + * Find the "decoder" configuration block for the specified plugin. + * + * @param plugin_name the name of the decoder plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +decoder_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) { + const char *name = param->GetBlockValue("plugin"); + if (name == NULL) + FormatFatalError("decoder configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void decoder_plugin_init_all(void) +{ + struct config_param empty; + + 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); + + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (decoder_plugin_init(plugin, *param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled(plugin) + decoder_plugin_finish(plugin); +} diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx new file mode 100644 index 000000000..8dab8724e --- /dev/null +++ b/src/DecoderList.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LIST_HXX +#define MPD_DECODER_LIST_HXX + +struct decoder_plugin; + +extern const struct decoder_plugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + +#define decoder_plugins_for_each(plugin) \ + for (const struct decoder_plugin *plugin, \ + *const*decoder_plugin_iterator = &decoder_plugins[0]; \ + (plugin = *decoder_plugin_iterator) != NULL; \ + ++decoder_plugin_iterator) + +#define decoder_plugins_for_each_enabled(plugin) \ + decoder_plugins_for_each(plugin) \ + if (decoder_plugins_enabled[decoder_plugin_iterator - decoder_plugins]) + +/* 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, + const struct decoder_plugin *plugin); + +const struct decoder_plugin * +decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); + +const struct decoder_plugin * +decoder_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void decoder_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void decoder_plugin_deinit_all(void); + +#endif diff --git a/src/DecoderPlugin.cxx b/src/DecoderPlugin.cxx new file mode 100644 index 000000000..9dce4b21f --- /dev/null +++ b/src/DecoderPlugin.cxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderPlugin.hxx" +#include "util/StringUtil.hxx" + +#include <assert.h> + +bool +decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, + const char *suffix) +{ + assert(plugin != nullptr); + assert(suffix != nullptr); + + return plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix); + +} + +bool +decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, + const char *mime_type) +{ + assert(plugin != nullptr); + assert(mime_type != nullptr); + + return plugin->mime_types != nullptr && + string_array_contains(plugin->mime_types, mime_type); +} diff --git a/src/DecoderPlugin.hxx b/src/DecoderPlugin.hxx new file mode 100644 index 000000000..693b8feee --- /dev/null +++ b/src/DecoderPlugin.hxx @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PLUGIN_HXX +#define MPD_DECODER_PLUGIN_HXX + +struct config_param; +struct input_stream; +struct Tag; +struct tag_handler; + +/** + * Opaque handle which the decoder plugin passes to the functions in + * this header. + */ +struct decoder; + +struct decoder_plugin { + const char *name; + + /** + * Initialize the decoder plugin. Optional method. + * + * @param param a configuration block for this plugin, or nullptr + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const config_param ¶m); + + /** + * Deinitialize a decoder plugin which was initialized + * successfully. Optional method. + */ + void (*finish)(void); + + /** + * Decode a stream (data read from an #input_stream object). + * + * Either implement this method or file_decode(). If + * possible, it is recommended to implement this method, + * because it is more versatile. + */ + void (*stream_decode)(struct decoder *decoder, + struct input_stream *is); + + /** + * Decode a local file. + * + * Either implement this method or stream_decode(). + */ + void (*file_decode)(struct decoder *decoder, const char *path_fs); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_file)(const char *path_fs, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_stream)(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * @brief Return a "virtual" filename for subtracks in + * container formats like flac + * @param const char* pathname full pathname for the file on fs + * @param const unsigned int tnum track number + * + * @return nullptr if there are no multiple files + * a filename for every single track according to tnum (param 2) + * do not include full pathname here, just the "virtual" file + */ + char* (*container_scan)(const char *path_fs, const unsigned int tnum); + + /* last element in these arrays must always be a nullptr: */ + const char *const*suffixes; + const char *const*mime_types; +}; + +/** + * Initialize a decoder plugin. + * + * @param param a configuration block for this plugin, or nullptr if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ +static inline bool +decoder_plugin_init(const struct decoder_plugin *plugin, + const config_param ¶m) +{ + return plugin->init != nullptr + ? plugin->init(param) + : true; +} + +/** + * Deinitialize a decoder plugin which was initialized successfully. + */ +static inline void +decoder_plugin_finish(const struct decoder_plugin *plugin) +{ + if (plugin->finish != nullptr) + plugin->finish(); +} + +/** + * Decode a stream. + */ +static inline void +decoder_plugin_stream_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, struct input_stream *is) +{ + plugin->stream_decode(decoder, is); +} + +/** + * Decode a file. + */ +static inline void +decoder_plugin_file_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, const char *path_fs) +{ + plugin->file_decode(decoder, path_fs); +} + +/** + * Read the tag of a file. + */ +static inline bool +decoder_plugin_scan_file(const struct decoder_plugin *plugin, + const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + return plugin->scan_file != nullptr + ? plugin->scan_file(path_fs, handler, handler_ctx) + : false; +} + +/** + * Read the tag of a stream. + */ +static inline bool +decoder_plugin_scan_stream(const struct decoder_plugin *plugin, + struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx) +{ + return plugin->scan_stream != nullptr + ? plugin->scan_stream(is, handler, handler_ctx) + : false; +} + +/** + * return "virtual" tracks in a container + */ +static inline char * +decoder_plugin_container_scan( const struct decoder_plugin *plugin, + const char* pathname, + const unsigned int tnum) +{ + 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/DecoderPrint.cxx b/src/DecoderPrint.cxx new file mode 100644 index 000000000..3f7f94937 --- /dev/null +++ b/src/DecoderPrint.cxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderPrint.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "Client.hxx" + +#include <assert.h> + +static void +decoder_plugin_print(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(Client *client) +{ + decoder_plugins_for_each_enabled(plugin) + decoder_plugin_print(client, plugin); +} diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx new file mode 100644 index 000000000..d94ba2cef --- /dev/null +++ b/src/DecoderPrint.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_DECODER_PRINT_HXX + +class Client; + +void +decoder_list_print(Client *client); + +#endif diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx new file mode 100644 index 000000000..1aa60ce3f --- /dev/null +++ b/src/DecoderThread.cxx @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DecoderError.hxx" +#include "DecoderPlugin.hxx" +#include "Song.hxx" +#include "system/FatalError.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "DecoderAPI.hxx" +#include "tag/Tag.hxx" +#include "InputStream.hxx" +#include "DecoderList.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "tag/ApeReplayGain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <unistd.h> +#include <stdio.h> /* for SEEK_SET */ + +static constexpr Domain decoder_thread_domain("decoder_thread"); + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #decoder_control object; must be locked + */ +static void +decoder_command_finished_locked(struct decoder_control *dc) +{ + assert(dc->command != DecoderCommand::NONE); + + dc->command = DecoderCommand::NONE; + + dc->client_cond.signal(); +} + +/** + * 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 #DecoderCommand::STOP is + * received, NULL on error + */ +static struct input_stream * +decoder_input_stream_open(struct decoder_control *dc, const char *uri) +{ + Error error; + + input_stream *is = input_stream::Open(uri, dc->mutex, dc->cond, error); + if (is == NULL) { + if (error.IsDefined()) + LogError(error); + + return NULL; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + dc->Lock(); + + is->Update(); + while (!is->ready && + dc->command != DecoderCommand::STOP) { + dc->Wait(); + + is->Update(); + } + + if (!is->Check(error)) { + dc->Unlock(); + + LogError(error); + return NULL; + } + + dc->Unlock(); + + return is; +} + +static bool +decoder_stream_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, + struct input_stream *input_stream) +{ + assert(plugin != NULL); + assert(plugin->stream_decode != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(input_stream != NULL); + assert(input_stream->ready); + assert(decoder->dc->state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin->name); + + if (decoder->dc->command == DecoderCommand::STOP) + return true; + + /* rewind the stream, so each plugin gets a fresh start */ + input_stream->Seek(0, SEEK_SET, IgnoreError()); + + decoder->dc->Unlock(); + + decoder_plugin_stream_decode(plugin, decoder, input_stream); + + decoder->dc->Lock(); + + assert(decoder->dc->state == DecoderState::START || + decoder->dc->state == DecoderState::DECODE); + + return decoder->dc->state != DecoderState::START; +} + +static bool +decoder_file_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, const char *path) +{ + assert(plugin != NULL); + assert(plugin->file_decode != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(path != NULL); + assert(g_path_is_absolute(path)); + assert(decoder->dc->state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin->name); + + if (decoder->dc->command == DecoderCommand::STOP) + return true; + + decoder->dc->Unlock(); + + decoder_plugin_file_decode(plugin, decoder, path); + + decoder->dc->Lock(); + + assert(decoder->dc->state == DecoderState::START || + decoder->dc->state == DecoderState::DECODE); + + return decoder->dc->state != DecoderState::START; +} + +/** + * Hack to allow tracking const decoder plugins in a GSList. + */ +static inline gpointer +deconst_plugin(const struct decoder_plugin *plugin) +{ + return const_cast<struct decoder_plugin *>(plugin); +} + +/** + * 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; + + if (is->mime.empty()) + return false; + + while ((plugin = decoder_plugin_from_mime_type(is->mime.c_str(), + 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)); + } + + return false; +} + +/** + * 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); + + const char *suffix = uri_get_suffix(uri); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + 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)); + } + + 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; + + dc->Unlock(); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == NULL) { + dc->Lock(); + return false; + } + + dc->Lock(); + + GSList *tried = NULL; + + success = dc->command == DecoderCommand::STOP || + /* first we try mime types: */ + 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); + + dc->Unlock(); + input_stream->Close(); + dc->Lock(); + + 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; + + dc->Unlock(); + + decoder_load_replay_gain(decoder, path_fs); + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->file_decode != NULL) { + dc->Lock(); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + dc->Unlock(); + } 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; + + dc->Lock(); + + success = decoder_stream_decode(plugin, decoder, + input_stream); + + dc->Unlock(); + + input_stream->Close(); + + if (success) { + dc->Lock(); + return true; + } + } + } + + dc->Lock(); + return false; +} + +static void +decoder_run_song(struct decoder_control *dc, + const Song *song, const char *uri) +{ + decoder decoder(dc, dc->start_ms > 0, + song->tag != NULL && song->IsFile() + ? new Tag(*song->tag) : nullptr); + int ret; + + dc->state = DecoderState::START; + + decoder_command_finished_locked(dc); + + ret = song->IsFile() + ? decoder_run_file(&decoder, uri) + : decoder_run_stream(&decoder, uri); + + dc->Unlock(); + + /* flush the last chunk */ + + if (decoder.chunk != NULL) + decoder_flush_chunk(&decoder); + + dc->Lock(); + + if (ret) + dc->state = DecoderState::STOP; + else { + dc->state = DecoderState::ERROR; + + const char *error_uri = song->uri; + char *allocated = uri_remove_auth(error_uri); + if (allocated != NULL) + error_uri = allocated; + + dc->error.Format(decoder_domain, + "Failed to decode %s", error_uri); + g_free(allocated); + } + + dc->client_cond.signal(); +} + +static void +decoder_run(struct decoder_control *dc) +{ + dc->ClearError(); + + const Song *song = dc->song; + char *uri; + + assert(song != NULL); + + if (song->IsFile()) + uri = g_strdup(map_song_fs(song).c_str()); + else + uri = song->GetURI(); + + if (uri == NULL) { + dc->state = DecoderState::ERROR; + dc->error.Set(decoder_domain, "Failed to map song"); + + decoder_command_finished_locked(dc); + return; + } + + decoder_run_song(dc, song, uri); + g_free(uri); + +} + +static gpointer +decoder_task(gpointer arg) +{ + struct decoder_control *dc = (struct decoder_control *)arg; + + dc->Lock(); + + do { + assert(dc->state == DecoderState::STOP || + dc->state == DecoderState::ERROR); + + switch (dc->command) { + case DecoderCommand::START: + dc->MixRampStart(nullptr); + dc->MixRampPrevEnd(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 DecoderCommand::SEEK: + decoder_run(dc); + break; + + case DecoderCommand::STOP: + decoder_command_finished_locked(dc); + break; + + case DecoderCommand::NONE: + dc->Wait(); + break; + } + } while (dc->command != DecoderCommand::NONE || !dc->quit); + + dc->Unlock(); + + return NULL; +} + +void +decoder_thread_start(struct decoder_control *dc) +{ + assert(dc->thread == NULL); + + dc->quit = false; + +#if GLIB_CHECK_VERSION(2,32,0) + dc->thread = g_thread_new("thread", decoder_task, dc); +#else + GError *e = NULL; + dc->thread = g_thread_create(decoder_task, dc, true, &e); + if (dc->thread == NULL) + FatalError("Failed to spawn decoder task", e); +#endif +} diff --git a/src/DecoderThread.hxx b/src/DecoderThread.hxx new file mode 100644 index 000000000..8efaa2fca --- /dev/null +++ b/src/DecoderThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_THREAD_HXX +#define MPD_DECODER_THREAD_HXX + +struct decoder_control; + +void +decoder_thread_start(struct decoder_control *dc); + +#endif diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx new file mode 100644 index 000000000..cf95998ad --- /dev/null +++ b/src/DespotifyUtils.cxx @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DespotifyUtils.hxx" +#include "tag/Tag.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +const Domain despotify_domain("despotify"); + +static struct despotify_session *g_session; +static void (*registered_callbacks[8])(struct despotify_session *, + int, void *, void *); +static void *registered_callback_data[8]; + +static void +callback(struct despotify_session* ds, int sig, + void *data, gcc_unused void *callback_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; + void *cb_data = registered_callback_data[i]; + + if (cb) + cb(ds, sig, data, cb_data); + } +} + +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (!registered_callbacks[i]) { + registered_callbacks[i] = cb; + registered_callback_data[i] = cb_data; + + return true; + } + } + + return false; +} + +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (registered_callbacks[i] == cb) { + registered_callbacks[i] = NULL; + } + } +} + + +Tag * +mpd_despotify_tag_from_track(struct ds_track *track) +{ + char tracknum[20]; + char comment[80]; + char date[20]; + + Tag *tag = new Tag(); + + if (!track->has_meta_data) + return tag; + + snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); + snprintf(date, sizeof(date), "%d", track->year); + snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", + track->file_bitrate / 1000, + track->geo_restricted ? "" : "not "); + tag->AddItem(TAG_TITLE, track->title); + tag->AddItem(TAG_ARTIST, track->artist->name); + tag->AddItem(TAG_TRACK, tracknum); + tag->AddItem(TAG_ALBUM, track->album); + tag->AddItem(TAG_DATE, date); + tag->AddItem(TAG_COMMENT, comment); + tag->time = track->length / 1000; + + return tag; +} + +struct despotify_session *mpd_despotify_get_session(void) +{ + const char *user; + const char *passwd; + bool high_bitrate; + + if (g_session) + return g_session; + + user = config_get_string(CONF_DESPOTIFY_USER, NULL); + passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL); + high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + + if (user == NULL || passwd == NULL) { + LogDebug(despotify_domain, + "disabling despotify because account is not configured"); + return nullptr; + } + + if (!despotify_init()) { + LogWarning(despotify_domain, "Can't initialize despotify"); + return nullptr; + } + + g_session = despotify_init_client(callback, NULL, + high_bitrate, true); + if (!g_session) { + LogWarning(despotify_domain, + "Can't initialize despotify client"); + return nullptr; + } + + if (!despotify_authenticate(g_session, user, passwd)) { + LogWarning(despotify_domain, + "Can't authenticate despotify session"); + despotify_exit(g_session); + return nullptr; + } + + return g_session; +} diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx new file mode 100644 index 000000000..b0f8f37c4 --- /dev/null +++ b/src/DespotifyUtils.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DESPOTIFY_H +#define MPD_DESPOTIFY_H + +struct Tag; +struct despotify_session; +struct ds_track; + +extern const class Domain despotify_domain; + +/** + * Return the current despotify session. + * + * If the session isn't initialized, this function will initialize + * it and connect to Spotify. + * + * @return a pointer to the despotify session, or NULL if it can't + * be initialized (e.g., if the configuration isn't supplied) + */ +struct despotify_session *mpd_despotify_get_session(void); + +/** + * Create a MPD tags structure from a spotify track + * + * @param track the track to convert + * + * @return a pointer to the filled in tags structure + */ +Tag * +mpd_despotify_tag_from_track(struct ds_track *track); + +/** + * Register a despotify callback. + * + * Despotify calls this e.g., when a track ends. + * + * @param cb the callback + * @param cb_data the data to pass to the callback + * + * @return true if the callback could be registered + */ +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data); + +/** + * Unregister a despotify callback. + * + * @param cb the callback to unregister. + */ +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); + +#endif + diff --git a/src/Directory.cxx b/src/Directory.cxx new file mode 100644 index 000000000..261d16385 --- /dev/null +++ b/src/Directory.cxx @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Directory.hxx" +#include "SongFilter.hxx" +#include "PlaylistVector.hxx" +#include "DatabaseLock.hxx" +#include "SongSort.hxx" +#include "Song.hxx" +#include "util/Error.hxx" + +extern "C" { +#include "util/list_sort.h" +} + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +inline Directory * +Directory::Allocate(const char *path) +{ + assert(path != NULL); + + const size_t path_size = strlen(path) + 1; + Directory *directory = + (Directory *)g_malloc0(sizeof(*directory) + - sizeof(directory->path) + + path_size); + new(directory) Directory(path); + + return directory; +} + +Directory::Directory() +{ + INIT_LIST_HEAD(&children); + INIT_LIST_HEAD(&songs); + + path[0] = 0; +} + +Directory::Directory(const char *_path) +{ + INIT_LIST_HEAD(&children); + INIT_LIST_HEAD(&songs); + + strcpy(path, _path); +} + +Directory::~Directory() +{ + Song *song, *ns; + directory_for_each_song_safe(song, ns, this) + song->Free(); + + Directory *child, *n; + directory_for_each_child_safe(child, n, this) + child->Free(); +} + +Directory * +Directory::NewGeneric(const char *path, Directory *parent) +{ + assert(path != NULL); + assert((*path == 0) == (parent == NULL)); + + Directory *directory = Allocate(path); + + directory->parent = parent; + + return directory; +} + +void +Directory::Free() +{ + this->Directory::~Directory(); + g_free(this); +} + +void +Directory::Delete() +{ + assert(holding_db_lock()); + assert(parent != nullptr); + + list_del(&siblings); + Free(); +} + +const char * +Directory::GetName() const +{ + assert(!IsRoot()); + assert(path != nullptr); + + const char *slash = strrchr(path, '/'); + assert((slash == nullptr) == parent->IsRoot()); + + return slash != NULL + ? slash + 1 + : path; +} + +Directory * +Directory::CreateChild(const char *name_utf8) +{ + assert(holding_db_lock()); + assert(name_utf8 != NULL); + assert(*name_utf8 != 0); + + char *allocated; + const char *path_utf8; + if (IsRoot()) { + allocated = NULL; + path_utf8 = name_utf8; + } else { + allocated = g_strconcat(GetPath(), + "/", name_utf8, NULL); + path_utf8 = allocated; + } + + Directory *child = NewGeneric(path_utf8, this); + g_free(allocated); + + list_add_tail(&child->siblings, &children); + return child; +} + +const Directory * +Directory::FindChild(const char *name) const +{ + assert(holding_db_lock()); + + const Directory *child; + directory_for_each_child(child, this) + if (strcmp(child->GetName(), name) == 0) + return child; + + return NULL; +} + +void +Directory::PruneEmpty() +{ + assert(holding_db_lock()); + + Directory *child, *n; + directory_for_each_child_safe(child, n, this) { + child->PruneEmpty(); + + if (child->IsEmpty()) + child->Delete(); + } +} + +Directory * +Directory::LookupDirectory(const char *uri) +{ + assert(holding_db_lock()); + assert(uri != NULL); + + if (isRootDirectory(uri)) + return this; + + char *duplicated = g_strdup(uri), *name = duplicated; + + Directory *d = this; + while (1) { + char *slash = strchr(name, '/'); + if (slash == name) { + d = NULL; + break; + } + + if (slash != NULL) + *slash = '\0'; + + d = d->FindChild(name); + if (d == NULL || slash == NULL) + break; + + name = slash + 1; + } + + g_free(duplicated); + + return d; +} + +void +Directory::AddSong(Song *song) +{ + assert(holding_db_lock()); + assert(song != NULL); + assert(song->parent == this); + + list_add_tail(&song->siblings, &songs); +} + +void +Directory::RemoveSong(Song *song) +{ + assert(holding_db_lock()); + assert(song != NULL); + assert(song->parent == this); + + list_del(&song->siblings); +} + +const Song * +Directory::FindSong(const char *name_utf8) const +{ + assert(holding_db_lock()); + assert(name_utf8 != NULL); + + Song *song; + directory_for_each_song(song, this) { + assert(song->parent == this); + + if (strcmp(song->uri, name_utf8) == 0) + return song; + } + + return NULL; +} + +Song * +Directory::LookupSong(const char *uri) +{ + char *duplicated, *base; + + assert(holding_db_lock()); + assert(uri != NULL); + + duplicated = g_strdup(uri); + base = strrchr(duplicated, '/'); + + Directory *d = this; + if (base != NULL) { + *base++ = 0; + d = d->LookupDirectory(duplicated); + if (d == nullptr) { + g_free(duplicated); + return NULL; + } + } else + base = duplicated; + + Song *song = d->FindSong(base); + assert(song == NULL || song->parent == d); + + g_free(duplicated); + return song; + +} + +static int +directory_cmp(gcc_unused void *priv, + struct list_head *_a, struct list_head *_b) +{ + const Directory *a = (const Directory *)_a; + const Directory *b = (const Directory *)_b; + return g_utf8_collate(a->path, b->path); +} + +void +Directory::Sort() +{ + assert(holding_db_lock()); + + list_sort(NULL, &children, directory_cmp); + song_list_sort(&songs); + + Directory *child; + directory_for_each_child(child, this) + child->Sort(); +} + +bool +Directory::Walk(bool recursive, const SongFilter *filter, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + assert(!error.IsDefined()); + + if (visit_song) { + Song *song; + directory_for_each_song(song, this) + if ((filter == nullptr || filter->Match(*song)) && + !visit_song(*song, error)) + return false; + } + + if (visit_playlist) { + for (const PlaylistInfo &p : playlists) + if (!visit_playlist(p, *this, error)) + return false; + } + + Directory *child; + directory_for_each_child(child, this) { + if (visit_directory && + !visit_directory(*child, error)) + return false; + + if (recursive && + !child->Walk(recursive, filter, + visit_directory, visit_song, visit_playlist, + error)) + return false; + } + + return true; +} diff --git a/src/Directory.hxx b/src/Directory.hxx new file mode 100644 index 000000000..928f04163 --- /dev/null +++ b/src/Directory.hxx @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DIRECTORY_HXX +#define MPD_DIRECTORY_HXX + +#include "check.h" +#include "util/list.h" +#include "gcc.h" +#include "DatabaseVisitor.hxx" +#include "PlaylistVector.hxx" + +#include <sys/types.h> + +#define DEVICE_INARCHIVE (dev_t)(-1) +#define DEVICE_CONTAINER (dev_t)(-2) + +#define directory_for_each_child(pos, directory) \ + list_for_each_entry(pos, &directory->children, siblings) + +#define directory_for_each_child_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->children, siblings) + +#define directory_for_each_song(pos, directory) \ + list_for_each_entry(pos, &directory->songs, siblings) + +#define directory_for_each_song_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->songs, siblings) + +struct Song; +struct db_visitor; +class SongFilter; +class Error; + +struct Directory { + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) in the root + * directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head siblings; + + /** + * A doubly linked list of child directories. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head children; + + /** + * A doubly linked list of songs within this directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head songs; + + PlaylistVector playlists; + + Directory *parent; + time_t mtime; + ino_t inode; + dev_t device; + bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ + char path[sizeof(long)]; + +protected: + Directory(const char *path); + + gcc_malloc gcc_nonnull_all + static Directory *Allocate(const char *path); + +public: + /** + * Default constructor, needed for #detached_root. + */ + Directory(); + ~Directory(); + + /** + * Generic constructor for #Directory object. + */ + gcc_malloc + static Directory *NewGeneric(const char *path_utf8, Directory *parent); + + /** + * Create a new root #Directory object. + */ + gcc_malloc + static Directory *NewRoot() { + return NewGeneric("", nullptr); + } + + /** + * Free this #Directory object (and the whole object tree within it), + * assuming it was already removed from the parent. + */ + void Free(); + + /** + * Remove this #Directory object from its parent and free it. This + * must not be called with the root Directory. + * + * Caller must lock the #db_mutex. + */ + void Delete(); + + /** + * Create a new #Directory object as a child of the given one. + * + * Caller must lock the #db_mutex. + * + * @param name_utf8 the UTF-8 encoded name of the new sub directory + */ + gcc_malloc + Directory *CreateChild(const char *name_utf8); + + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + const Directory *FindChild(const char *name) const; + + gcc_pure + Directory *FindChild(const char *name) { + const Directory *cthis = this; + return const_cast<Directory *>(cthis->FindChild(name)); + } + + /** + * Look up a sub directory, and create the object if it does not + * exist. + * + * Caller must lock the #db_mutex. + */ + Directory *MakeChild(const char *name_utf8) { + Directory *child = FindChild(name_utf8); + if (child == nullptr) + child = CreateChild(name_utf8); + return child; + } + + /** + * Looks up a directory by its relative URI. + * + * @param uri the relative URI + * @return the Directory, or NULL if none was found + */ + gcc_pure + Directory *LookupDirectory(const char *uri); + + gcc_pure + bool IsEmpty() const { + return list_empty(&children) && + list_empty(&songs) && + playlists.empty(); + } + + gcc_pure + const char *GetPath() const { + return path; + } + + /** + * Returns the base name of the directory. + */ + gcc_pure + const char *GetName() const; + + /** + * Is this the root directory of the music database? + */ + gcc_pure + bool IsRoot() const { + return parent == NULL; + } + + /** + * Look up a song in this directory by its name. + * + * Caller must lock the #db_mutex. + */ + gcc_pure + const Song *FindSong(const char *name_utf8) const; + + gcc_pure + Song *FindSong(const char *name_utf8) { + const Directory *cthis = this; + return const_cast<Song *>(cthis->FindSong(name_utf8)); + } + + /** + * Looks up a song by its relative URI. + * + * Caller must lock the #db_mutex. + * + * @param uri the relative URI + * @return the song, or NULL if none was found + */ + gcc_pure + Song *LookupSong(const char *uri); + + /** + * Add a song object to this directory. Its "parent" attribute must + * be set already. + */ + void AddSong(Song *song); + + /** + * Remove a song object from this directory (which effectively + * invalidates the song object, because the "parent" attribute becomes + * stale), but does not free it. + */ + void RemoveSong(Song *song); + + /** + * Caller must lock the #db_mutex. + */ + void PruneEmpty(); + + /** + * Sort all directory entries recursively. + * + * Caller must lock the #db_mutex. + */ + void Sort(); + + /** + * Caller must lock #db_mutex. + */ + bool Walk(bool recursive, const SongFilter *match, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const; +}; + +static inline bool +isRootDirectory(const char *name) +{ + return name[0] == 0 || (name[0] == '/' && name[1] == 0); +} + +#endif diff --git a/src/DirectorySave.cxx b/src/DirectorySave.cxx new file mode 100644 index 000000000..c39ac1edc --- /dev/null +++ b/src/DirectorySave.cxx @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DirectorySave.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "SongSave.hxx" +#include "PlaylistDatabase.hxx" +#include "TextFile.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_MTIME "mtime: " +#define DIRECTORY_BEGIN "begin: " +#define DIRECTORY_END "end: " + +static constexpr Domain directory_domain("directory"); + +void +directory_save(FILE *fp, const Directory *directory) +{ + if (!directory->IsRoot()) { + fprintf(fp, DIRECTORY_MTIME "%lu\n", + (unsigned long)directory->mtime); + + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory->GetPath()); + } + + Directory *cur; + directory_for_each_child(cur, directory) { + char *base = g_path_get_basename(cur->path); + + fprintf(fp, DIRECTORY_DIR "%s\n", base); + g_free(base); + + directory_save(fp, cur); + + if (ferror(fp)) + return; + } + + Song *song; + directory_for_each_song(song, directory) + song_save(fp, song); + + playlist_vector_save(fp, directory->playlists); + + if (!directory->IsRoot()) + fprintf(fp, DIRECTORY_END "%s\n", directory->GetPath()); +} + +static Directory * +directory_load_subdir(TextFile &file, Directory *parent, const char *name, + Error &error) +{ + bool success; + + if (parent->FindChild(name) != nullptr) { + error.Format(directory_domain, + "Duplicate subdirectory '%s'", name); + return NULL; + } + + Directory *directory = parent->CreateChild(name); + + const char *line = file.ReadLine(); + if (line == NULL) { + error.Set(directory_domain, "Unexpected end of file"); + directory->Delete(); + return NULL; + } + + if (g_str_has_prefix(line, DIRECTORY_MTIME)) { + directory->mtime = + g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, + NULL, 10); + + line = file.ReadLine(); + if (line == NULL) { + error.Set(directory_domain, "Unexpected end of file"); + directory->Delete(); + return NULL; + } + } + + if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { + error.Format(directory_domain, "Malformed line: %s", line); + directory->Delete(); + return NULL; + } + + success = directory_load(file, directory, error); + if (!success) { + directory->Delete(); + return NULL; + } + + return directory; +} + +bool +directory_load(TextFile &file, Directory *directory, Error &error) +{ + const char *line; + + while ((line = file.ReadLine()) != NULL && + !g_str_has_prefix(line, DIRECTORY_END)) { + if (g_str_has_prefix(line, DIRECTORY_DIR)) { + Directory *subdir = + directory_load_subdir(file, directory, + line + sizeof(DIRECTORY_DIR) - 1, + error); + if (subdir == NULL) + return false; + } else if (g_str_has_prefix(line, SONG_BEGIN)) { + const char *name = line + sizeof(SONG_BEGIN) - 1; + Song *song; + + if (directory->FindSong(name) != nullptr) { + error.Format(directory_domain, + "Duplicate song '%s'", name); + return false; + } + + song = song_load(file, directory, name, error); + if (song == NULL) + return false; + + directory->AddSong(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(file, directory->playlists, + name, error)) { + g_free(name); + return false; + } + + g_free(name); + } else { + error.Format(directory_domain, + "Malformed line: %s", line); + return false; + } + } + + return true; +} diff --git a/src/DirectorySave.hxx b/src/DirectorySave.hxx new file mode 100644 index 000000000..46a3b4462 --- /dev/null +++ b/src/DirectorySave.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DIRECTORY_SAVE_HXX +#define MPD_DIRECTORY_SAVE_HXX + +#include <stdio.h> + +struct Directory; +class TextFile; +class Error; + +void +directory_save(FILE *fp, const Directory *directory); + +bool +directory_load(TextFile &file, Directory *directory, Error &error); + +#endif diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx new file mode 100644 index 000000000..b3397f25c --- /dev/null +++ b/src/EncoderAPI.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This header is included by encoder plugins. + * + */ + +#ifndef MPD_ENCODER_API_HXX +#define MPD_ENCODER_API_HXX + +#include "EncoderPlugin.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "ConfigData.hxx" + +#endif diff --git a/src/EncoderList.cxx b/src/EncoderList.cxx new file mode 100644 index 000000000..2305548b9 --- /dev/null +++ b/src/EncoderList.cxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "EncoderList.hxx" +#include "EncoderPlugin.hxx" +#include "encoder/NullEncoderPlugin.hxx" +#include "encoder/WaveEncoderPlugin.hxx" +#include "encoder/VorbisEncoderPlugin.hxx" +#include "encoder/OpusEncoderPlugin.hxx" +#include "encoder/FlacEncoderPlugin.hxx" +#include "encoder/LameEncoderPlugin.hxx" +#include "encoder/TwolameEncoderPlugin.hxx" + +#include <string.h> + +const EncoderPlugin *const encoder_plugins[] = { + &null_encoder_plugin, +#ifdef ENABLE_VORBIS_ENCODER + &vorbis_encoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_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 +}; + +const EncoderPlugin * +encoder_plugin_get(const char *name) +{ + encoder_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} diff --git a/src/EncoderList.hxx b/src/EncoderList.hxx new file mode 100644 index 000000000..feaf7a6d1 --- /dev/null +++ b/src/EncoderList.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_LIST_HXX +#define MPD_ENCODER_LIST_HXX + +struct EncoderPlugin; + +extern const EncoderPlugin *const encoder_plugins[]; + +#define encoder_plugins_for_each(plugin) \ + for (const EncoderPlugin *plugin, \ + *const*encoder_plugin_iterator = &encoder_plugins[0]; \ + (plugin = *encoder_plugin_iterator) != NULL; \ + ++encoder_plugin_iterator) + +/** + * Looks up an encoder plugin by its name. + * + * @param name the encoder name to look for + * @return the encoder plugin with the specified name, or NULL if none + * was found + */ +const EncoderPlugin * +encoder_plugin_get(const char *name); + +#endif diff --git a/src/EncoderPlugin.hxx b/src/EncoderPlugin.hxx new file mode 100644 index 000000000..1bca66e0a --- /dev/null +++ b/src/EncoderPlugin.hxx @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_PLUGIN_HXX +#define MPD_ENCODER_PLUGIN_HXX + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> + +struct EncoderPlugin; +struct AudioFormat; +struct config_param; +struct Tag; +class Error; + +struct Encoder { + const EncoderPlugin &plugin; + +#ifndef NDEBUG + bool open, pre_tag, tag, end; +#endif + + explicit Encoder(const EncoderPlugin &_plugin) + :plugin(_plugin) +#ifndef NDEBUG + , open(false) +#endif + {} +}; + +struct EncoderPlugin { + const char *name; + + Encoder *(*init)(const config_param ¶m, + Error &error); + + void (*finish)(Encoder *encoder); + + bool (*open)(Encoder *encoder, + AudioFormat &audio_format, + Error &error); + + void (*close)(Encoder *encoder); + + bool (*end)(Encoder *encoder, Error &error); + + bool (*flush)(Encoder *encoder, Error &error); + + bool (*pre_tag)(Encoder *encoder, Error &error); + + bool (*tag)(Encoder *encoder, const Tag *tag, + Error &error); + + bool (*write)(Encoder *encoder, + const void *data, size_t length, + Error &error); + + size_t (*read)(Encoder *encoder, void *dest, size_t length); + + const char *(*get_mime_type)(Encoder *encoder); +}; + +/** + * Creates a new encoder object. + * + * @param plugin the encoder plugin + * @param param optional configuration + * @param error location to store the error occurring, or NULL to ignore errors. + * @return an encoder object on success, NULL on failure + */ +static inline Encoder * +encoder_init(const EncoderPlugin &plugin, const config_param ¶m, + Error &error_r) +{ + return plugin.init(param, error_r); +} + +/** + * Frees an encoder object. + * + * @param encoder the encoder + */ +static inline void +encoder_finish(Encoder *encoder) +{ + assert(!encoder->open); + + encoder->plugin.finish(encoder); +} + +/** + * Opens an encoder object. You must call this prior to using it. + * Before you free it, you must call encoder_close(). You may open + * and close (reuse) one encoder any number of times. + * + * After this function returns successfully and before the first + * encoder_write() call, you should invoke encoder_read() to obtain + * the file header. + * + * @param encoder the encoder + * @param audio_format the encoder's input audio format; the plugin + * may modify the struct to adapt it to its abilities + * @param error location to store the error occurring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_open(Encoder *encoder, AudioFormat &audio_format, + Error &error) +{ + assert(!encoder->open); + + bool success = encoder->plugin.open(encoder, audio_format, error); +#ifndef NDEBUG + encoder->open = success; + encoder->pre_tag = encoder->tag = encoder->end = false; +#endif + return success; +} + +/** + * Closes an encoder object. This disables the encoder, and readies + * it for reusal by calling encoder_open() again. + * + * @param encoder the encoder + */ +static inline void +encoder_close(Encoder *encoder) +{ + assert(encoder->open); + + if (encoder->plugin.close != NULL) + encoder->plugin.close(encoder); + +#ifndef NDEBUG + encoder->open = false; +#endif +} + +/** + * Ends the stream: flushes the encoder object, generate an + * end-of-stream marker (if applicable), make everything which might + * currently be buffered available by encoder_read(). + * + * After this function has been called, the encoder may not be usable + * for more data, and only encoder_read() and encoder_close() can be + * called. + * + * @param encoder the encoder + * @param error location to store the error occuring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_end(Encoder *encoder, Error &error) +{ + assert(encoder->open); + assert(!encoder->end); + +#ifndef NDEBUG + encoder->end = true; +#endif + + /* this method is optional */ + return encoder->plugin.end != NULL + ? encoder->plugin.end(encoder, error) + : true; +} + +/** + * Flushes an encoder object, make everything which might currently be + * buffered available by encoder_read(). + * + * @param encoder the encoder + * @param error location to store the error occurring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_flush(Encoder *encoder, Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + + /* this method is optional */ + return encoder->plugin.flush != NULL + ? encoder->plugin.flush(encoder, error) + : true; +} + +/** + * Prepare for sending a tag to the encoder. This is used by some + * encoders to flush the previous sub-stream, in preparation to begin + * a new one. + * + * @param encoder the encoder + * @param tag the tag object + * @param error location to store the error occuring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_pre_tag(Encoder *encoder, Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + + /* this method is optional */ + bool success = encoder->plugin.pre_tag != NULL + ? encoder->plugin.pre_tag(encoder, error) + : true; + +#ifndef NDEBUG + encoder->pre_tag = success; +#endif + return success; +} + +/** + * Sends a tag to the encoder. + * + * Instructions: call encoder_pre_tag(); then obtain flushed data with + * encoder_read(); finally call encoder_tag(). + * + * @param encoder the encoder + * @param tag the tag object + * @param error location to store the error occurring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_tag(Encoder *encoder, const Tag *tag, Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(encoder->tag); + assert(!encoder->end); + +#ifndef NDEBUG + encoder->tag = false; +#endif + + /* this method is optional */ + return encoder->plugin.tag != NULL + ? encoder->plugin.tag(encoder, tag, error) + : true; +} + +/** + * Writes raw PCM data to the encoder. + * + * @param encoder the encoder + * @param data the buffer containing PCM samples + * @param length the length of the buffer in bytes + * @param error location to store the error occurring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_write(Encoder *encoder, const void *data, size_t length, + Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + + return encoder->plugin.write(encoder, data, length, error); +} + +/** + * Reads encoded data from the encoder. + * + * Call this repeatedly until no more data is returned. + * + * @param encoder the encoder + * @param dest the destination buffer to copy to + * @param length the maximum length of the destination buffer + * @return the number of bytes written to #dest + */ +static inline size_t +encoder_read(Encoder *encoder, void *dest, size_t length) +{ + assert(encoder->open); + assert(!encoder->pre_tag || !encoder->tag); + +#ifndef NDEBUG + if (encoder->pre_tag) { + encoder->pre_tag = false; + encoder->tag = true; + } +#endif + + 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(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/ExcludeList.cxx b/src/ExcludeList.cxx new file mode 100644 index 000000000..16bdbf383 --- /dev/null +++ b/src/ExcludeList.cxx @@ -0,0 +1,83 @@ + +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#include "config.h" +#include "ExcludeList.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <errno.h> + +static constexpr Domain exclude_list_domain("exclude_list"); + +bool +ExcludeList::LoadFile(const Path &path_fs) +{ + FILE *file = FOpen(path_fs, FOpenMode::ReadText); + if (file == NULL) { + const int e = errno; + if (e != ENOENT) { + const auto path_utf8 = path_fs.ToUTF8(); + FormatErrno(exclude_list_domain, + "Failed to open %s", + path_utf8.c_str()); + } + + return false; + } + + char line[1024]; + while (fgets(line, sizeof(line), file) != NULL) { + char *p = strchr(line, '#'); + if (p != NULL) + *p = 0; + + p = g_strstrip(line); + if (*p != 0) + patterns.emplace_front(p); + } + + fclose(file); + + return true; +} + +bool +ExcludeList::Check(const Path &name_fs) const +{ + assert(!name_fs.IsNull()); + + /* XXX include full path name in check */ + + for (const auto &i : patterns) + if (i.Check(name_fs.c_str())) + return true; + + return false; +} diff --git a/src/ExcludeList.hxx b/src/ExcludeList.hxx new file mode 100644 index 000000000..7111465a3 --- /dev/null +++ b/src/ExcludeList.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#ifndef MPD_EXCLUDE_H +#define MPD_EXCLUDE_H + +#include "gcc.h" + +#include <forward_list> + +#include <glib.h> + +class Path; + +class ExcludeList { + class Pattern { + GPatternSpec *pattern; + + public: + Pattern(const char *_pattern) + :pattern(g_pattern_spec_new(_pattern)) {} + + Pattern(Pattern &&other) + :pattern(other.pattern) { + other.pattern = nullptr; + } + + ~Pattern() { + g_pattern_spec_free(pattern); + } + + gcc_pure + bool Check(const char *name_fs) const { + return g_pattern_match_string(pattern, name_fs); + } + }; + + std::forward_list<Pattern> patterns; + +public: + gcc_pure + bool IsEmpty() const { + return patterns.empty(); + } + + /** + * Loads and parses a .mpdignore file. + */ + bool LoadFile(const Path &path_fs); + + /** + * Checks whether one of the patterns in the .mpdignore file matches + * the specified file name. + */ + bool Check(const Path &name_fs) const; +}; + + +#endif diff --git a/src/FilterConfig.cxx b/src/FilterConfig.cxx new file mode 100644 index 000000000..b9f6fe20f --- /dev/null +++ b/src/FilterConfig.cxx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FilterConfig.hxx" +#include "filter/ChainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "ConfigData.hxx" +#include "ConfigOption.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <string.h> + +/** + * Find the "filter" configuration block for the specified name. + * + * @param filter_template_name the name of the filter template + * @param error 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, Error &error) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) { + const char *name = param->GetBlockValue("name"); + if (name == NULL) { + error.Format(config_domain, + "filter configuration without 'name' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, filter_template_name) == 0) + return param; + } + + error.Format(config_domain, + "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 space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(Filter &chain, const char *spec, Error &error) +{ + + // Split on comma + gchar** tokens = g_strsplit_set(spec, ",", 255); + + unsigned added_filters = 0; + + // Add each name to the filter chain by instantiating an actual filter + char **template_names = tokens; + while (*template_names != NULL) { + // Squeeze whitespace + g_strstrip(*template_names); + + const struct config_param *cfg = + filter_plugin_config(*template_names, error); + 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 + Filter *f = filter_configured_new(*cfg, error); + if (f == NULL) { + // The error has already been set, just stop. + break; + } + + const char *plugin_name = cfg->GetBlockValue("plugin", + "unknown"); + + filter_chain_append(chain, plugin_name, f); + ++added_filters; + + ++template_names; + } + + g_strfreev(tokens); + + return added_filters; +} diff --git a/src/FilterConfig.hxx b/src/FilterConfig.hxx new file mode 100644 index 000000000..3e6553b8d --- /dev/null +++ b/src/FilterConfig.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_FILTER_CONFIG_HXX + +class Filter; +class Error; + +/** + * 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(Filter &chain, const char *spec, Error &error); + +#endif diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx new file mode 100644 index 000000000..ab648af1e --- /dev/null +++ b/src/FilterInternal.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_FILTER_INTERNAL_HXX + +#include <stddef.h> + +struct AudioFormat; +class Error; + +class Filter { +public: + virtual ~Filter() {} + + /** + * Opens the filter, preparing it for FilterPCM(). + * + * @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 occurring, or NULL + * to ignore errors. + * @return the format of outgoing data or + * AudioFormat::Undefined() on error + */ + virtual AudioFormat Open(AudioFormat &af, Error &error) = 0; + + /** + * Closes the filter. After that, you may call Open() again. + */ + virtual void Close() = 0; + + /** + * 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 occurring, or NULL + * to ignore errors. + * @return the destination buffer on success (will be + * invalidated by filter_close() or filter_filter()), NULL on + * error + */ + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, + Error &error) = 0; +}; + +#endif diff --git a/src/FilterPlugin.cxx b/src/FilterPlugin.cxx new file mode 100644 index 000000000..9afda3f73 --- /dev/null +++ b/src/FilterPlugin.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "ConfigData.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +Filter * +filter_new(const struct filter_plugin *plugin, + const config_param ¶m, Error &error) +{ + assert(plugin != NULL); + assert(!error.IsDefined()); + + return plugin->init(param, error); +} + +Filter * +filter_configured_new(const config_param ¶m, Error &error) +{ + assert(!error.IsDefined()); + + const char *plugin_name = param.GetBlockValue("plugin"); + if (plugin_name == NULL) { + error.Set(config_domain, "No filter plugin specified"); + return NULL; + } + + const filter_plugin *plugin = filter_plugin_by_name(plugin_name); + if (plugin == NULL) { + error.Format(config_domain, + "No such filter plugin: %s", plugin_name); + return NULL; + } + + return filter_new(plugin, param, error); +} diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx new file mode 100644 index 000000000..88c786120 --- /dev/null +++ b/src/FilterPlugin.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_FILTER_PLUGIN_HXX + +struct config_param; +class Filter; +class Error; + +struct filter_plugin { + const char *name; + + /** + * Allocates and configures a filter. + */ + Filter *(*init)(const config_param ¶m, Error &error); +}; + +/** + * 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 occurring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +Filter * +filter_new(const struct filter_plugin *plugin, + const config_param ¶m, Error &error); + +/** + * 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 occurring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +Filter * +filter_configured_new(const config_param ¶m, Error &error); + +#endif diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx new file mode 100644 index 000000000..c8aff8298 --- /dev/null +++ b/src/FilterRegistry.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FilterRegistry.hxx" +#include "FilterPlugin.hxx" + +#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/FilterRegistry.hxx b/src/FilterRegistry.hxx new file mode 100644 index 000000000..c21d8a597 --- /dev/null +++ b/src/FilterRegistry.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_FILTER_REGISTRY_HXX + +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/GlobalEvents.cxx b/src/GlobalEvents.cxx new file mode 100644 index 000000000..2f8569750 --- /dev/null +++ b/src/GlobalEvents.cxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "GlobalEvents.hxx" +#include "util/Manual.hxx" +#include "event/DeferredMonitor.hxx" +#include "gcc.h" + +#include <atomic> + +#include <assert.h> +#include <glib.h> + +namespace GlobalEvents { + class Monitor final : public DeferredMonitor { + public: + Monitor(EventLoop &_loop):DeferredMonitor(_loop) {} + + protected: + virtual void RunDeferred() override; + }; + + static Manual<Monitor> monitor; + + static std::atomic_uint flags; + static Handler handlers[MAX]; +} + +/** + * Invoke the callback for a certain event. + */ +static void +InvokeGlobalEvent(GlobalEvents::Event event) +{ + assert((unsigned)event < GlobalEvents::MAX); + assert(GlobalEvents::handlers[event] != NULL); + + GlobalEvents::handlers[event](); +} + +void +GlobalEvents::Monitor::RunDeferred() +{ + const unsigned f = flags.exchange(0); + + for (unsigned i = 0; i < MAX; ++i) + if (f & (1u << i)) + /* invoke the event handler */ + InvokeGlobalEvent(Event(i)); +} + +void +GlobalEvents::Initialize(EventLoop &loop) +{ + monitor.Construct(loop); +} + +void +GlobalEvents::Deinitialize() +{ + monitor.Destruct(); +} + +void +GlobalEvents::Register(Event event, Handler callback) +{ + assert((unsigned)event < MAX); + assert(handlers[event] == NULL); + + handlers[event] = callback; +} + +void +GlobalEvents::Emit(Event event) +{ + assert((unsigned)event < MAX); + + const unsigned mask = 1u << unsigned(event); + if (GlobalEvents::flags.fetch_or(mask) == 0) + monitor->Schedule(); +} diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx new file mode 100644 index 000000000..47ec6d070 --- /dev/null +++ b/src/GlobalEvents.hxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_GLOBAL_EVENTS_HXX +#define MPD_GLOBAL_EVENTS_HXX + +#ifdef WIN32 +#include <windows.h> +/* DELETE is a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef DELETE +#undef DELETE +#endif +#endif + +class EventLoop; + +namespace GlobalEvents { + enum Event { + /** database update was finished */ + UPDATE, + + /** during database update, a song was deleted */ + DELETE, + + /** an idle event was emitted */ + IDLE, + + /** must call playlist_sync() */ + PLAYLIST, + + /** the current song's tag has changed */ + TAG, + + /** a hardware mixer plugin has detected a change */ + MIXER, + +#ifdef WIN32 + /** shutdown requested */ + SHUTDOWN, +#endif + + MAX + }; + + typedef void (*Handler)(); + + void Initialize(EventLoop &loop); + + void Deinitialize(); + + void Register(Event event, Handler handler); + + void Emit(Event event); +} + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/IOThread.cxx b/src/IOThread.cxx new file mode 100644 index 000000000..dbe206af7 --- /dev/null +++ b/src/IOThread.cxx @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "IOThread.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "event/Loop.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <assert.h> + +static struct { + Mutex mutex; + Cond cond; + + EventLoop *loop; + GThread *thread; +} io; + +void +io_thread_run(void) +{ + assert(io_thread_inside()); + assert(io.loop != NULL); + + io.loop->Run(); +} + +static gpointer +io_thread_func(gcc_unused gpointer arg) +{ + /* lock+unlock to synchronize with io_thread_start(), to be + sure that io.thread is set */ + io.mutex.lock(); + io.mutex.unlock(); + + io_thread_run(); + return NULL; +} + +void +io_thread_init(void) +{ + assert(io.loop == NULL); + assert(io.thread == NULL); + + io.loop = new EventLoop(); +} + +void +io_thread_start() +{ + assert(io.loop != NULL); + assert(io.thread == NULL); + + const ScopeLock protect(io.mutex); + +#if GLIB_CHECK_VERSION(2,32,0) + io.thread = g_thread_new("io", io_thread_func, nullptr); +#else + GError *error = nullptr; + io.thread = g_thread_create(io_thread_func, NULL, true, &error); + if (io.thread == NULL) + FatalError(error); +#endif +} + +void +io_thread_quit(void) +{ + assert(io.loop != NULL); + + io.loop->Break(); +} + +void +io_thread_deinit(void) +{ + if (io.thread != NULL) { + io_thread_quit(); + + g_thread_join(io.thread); + } + + delete io.loop; +} + +EventLoop & +io_thread_get() +{ + assert(io.loop != nullptr); + + return *io.loop; +} + +bool +io_thread_inside(void) +{ + return io.thread != NULL && g_thread_self() == io.thread; +} diff --git a/src/IOThread.hxx b/src/IOThread.hxx new file mode 100644 index 000000000..cfa34a494 --- /dev/null +++ b/src/IOThread.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_IO_THREAD_HXX +#define MPD_IO_THREAD_HXX + +#include "gcc.h" + +class EventLoop; + +void +io_thread_init(void); + +void +io_thread_start(); + +/** + * Run the I/O event loop synchronously in the current thread. This + * can be called instead of io_thread_start(). For testing purposes + * only. + */ +void +io_thread_run(void); + +/** + * Ask the I/O thread to quit, but does not wait for it. Usually, you + * don't need to call this function, because io_thread_deinit() + * includes this. + */ +void +io_thread_quit(void); + +void +io_thread_deinit(void); + +gcc_pure +EventLoop & +io_thread_get(); + +/** + * Is the current thread the I/O thread? + */ +gcc_pure +bool +io_thread_inside(void); + +#endif diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx new file mode 100644 index 000000000..e4251d526 --- /dev/null +++ b/src/IcyMetaDataParser.cxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "IcyMetaDataParser.hxx" +#include "tag/Tag.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain icy_metadata_domain("icy_metadata"); + +void +IcyMetaDataParser::Reset() +{ + if (!IsDefined()) + return; + + if (data_rest == 0 && meta_size > 0) + g_free(meta_data); + + delete tag; + + data_rest = data_size; + meta_size = 0; +} + +size_t +IcyMetaDataParser::Data(size_t length) +{ + assert(length > 0); + + if (!IsDefined()) + return length; + + if (data_rest == 0) + return 0; + + if (length >= data_rest) { + length = data_rest; + data_rest = 0; + } else + data_rest -= length; + + return length; +} + +static void +icy_add_item(Tag &tag, enum tag_type type, const char *value) +{ + size_t length = strlen(value); + + if (length >= 2 && value[0] == '\'' && value[length - 1] == '\'') { + /* strip the single quotes */ + ++value; + length -= 2; + } + + if (length > 0) + tag.AddItem(type, value, length); +} + +static void +icy_parse_tag_item(Tag &tag, const char *item) +{ + gchar **p = g_strsplit(item, "=", 0); + + if (p[0] != nullptr && p[1] != nullptr) { + if (strcmp(p[0], "StreamTitle") == 0) + icy_add_item(tag, TAG_TITLE, p[1]); + else + FormatDebug(icy_metadata_domain, + "unknown icy-tag: '%s'", p[0]); + } + + g_strfreev(p); +} + +static Tag * +icy_parse_tag(const char *p) +{ + Tag *tag = new Tag(); + gchar **items = g_strsplit(p, ";", 0); + + for (unsigned i = 0; items[i] != nullptr; ++i) + icy_parse_tag_item(*tag, items[i]); + + g_strfreev(items); + + return tag; +} + +size_t +IcyMetaDataParser::Meta(const void *data, size_t length) +{ + const unsigned char *p = (const unsigned char *)data; + + assert(IsDefined()); + assert(data_rest == 0); + assert(length > 0); + + if (meta_size == 0) { + /* read meta_size from the first byte of a meta + block */ + meta_size = *p++ * 16; + if (meta_size == 0) { + /* special case: no metadata */ + data_rest = data_size; + return 1; + } + + /* 1 byte was consumed (must be re-added later for the + return value */ + --length; + + /* initialize metadata reader, allocate enough + memory (+1 for the null terminator) */ + meta_position = 0; + meta_data = (char *)g_malloc(meta_size + 1); + } + + assert(meta_position < meta_size); + + if (length > meta_size - meta_position) + length = meta_size - meta_position; + + memcpy(meta_data + meta_position, p, length); + meta_position += length; + + if (p != data) + /* re-add the first byte (which contained meta_size) */ + ++length; + + if (meta_position == meta_size) { + /* null-terminate the string */ + + meta_data[meta_size] = 0; + + /* parse */ + + delete tag; + + tag = icy_parse_tag(meta_data); + g_free(meta_data); + + /* change back to normal data mode */ + + meta_size = 0; + data_rest = data_size; + } + + return length; +} diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx new file mode 100644 index 000000000..6bcb09668 --- /dev/null +++ b/src/IcyMetaDataParser.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ICY_META_DATA_PARSER_HXX +#define MPD_ICY_META_DATA_PARSER_HXX + +#include <stddef.h> + +struct Tag; + +class IcyMetaDataParser { + size_t data_size, data_rest; + + size_t meta_size, meta_position; + char *meta_data; + + Tag *tag; + +public: + IcyMetaDataParser():data_size(0) {} + ~IcyMetaDataParser() { + Reset(); + } + + /** + * Initialize an enabled icy_metadata object with the specified + * data_size (from the icy-metaint HTTP response header). + */ + void Start(size_t _data_size) { + data_size = data_rest = _data_size; + meta_size = 0; + tag = nullptr; + } + + /** + * Resets the icy_metadata. Call this after rewinding the stream. + */ + void Reset(); + + /** + * Checks whether the icy_metadata object is enabled. + */ + bool IsDefined() const { + return data_size > 0; + } + + /** + * Evaluates data. Returns the number of bytes of normal data which + * can be read by the caller, but not more than "length". If the + * return value is smaller than "length", the caller should invoke + * icy_meta(). + */ + size_t Data(size_t length); + + /** + * Reads metadata from the stream. Returns the number of bytes + * consumed. If the return value is smaller than "length", the caller + * should invoke icy_data(). + */ + size_t Meta(const void *data, size_t length); + + Tag *ReadTag() { + Tag *result = tag; + tag = nullptr; + return result; + } +}; + +#endif diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx new file mode 100644 index 000000000..051a240ba --- /dev/null +++ b/src/IcyMetaDataServer.cxx @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "IcyMetaDataServer.hxx" +#include "Page.hxx" +#include "tag/Tag.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint) +{ + return g_strdup_printf("ICY 200 OK\r\n" + "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ + "icy-notice2:MPD - The music player daemon<BR>\r\n" + "icy-name: %s\r\n" /* TODO */ + "icy-genre: %s\r\n" /* TODO */ + "icy-url: %s\r\n" /* TODO */ + "icy-pub:1\r\n" + "icy-metaint:%d\r\n" + /* TODO "icy-br:%d\r\n" */ + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + name, + genre, + url, + metaint, + /* bitrate, */ + content_type); +} + +static char * +icy_server_metadata_string(const char *stream_title, const char* stream_url) +{ + gchar *icy_metadata; + guint meta_length; + + // The leading n is a placeholder for the length information + icy_metadata = g_strdup_printf("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); + + g_return_val_if_fail(icy_metadata, NULL); + + meta_length = strlen(icy_metadata); + + meta_length--; // subtract placeholder + + meta_length = ((int)meta_length / 16) + 1; + + icy_metadata[0] = meta_length; + + if (meta_length > 255) { + g_free(icy_metadata); + return NULL; + } + + return icy_metadata; +} + +Page * +icy_server_metadata_page(const Tag &tag, const enum tag_type *types) +{ + const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; + gint last_item, item; + guint position; + gchar *icy_string; + gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - + // "StreamTitle='';StreamUrl='';" + // = 4081 - 28 + stream_title[0] = '\0'; + + last_item = -1; + + while (*types != TAG_NUM_OF_ITEM_TYPES) { + const gchar *tag_item = tag.GetValue(*types++); + if (tag_item) + tag_items[++last_item] = tag_item; + } + + position = item = 0; + while (position < sizeof(stream_title) && item <= last_item) { + gint length = 0; + + length = g_strlcpy(stream_title + position, + tag_items[item++], + sizeof(stream_title) - position); + + position += length; + + if (item <= last_item) { + length = g_strlcpy(stream_title + position, + " - ", + sizeof(stream_title) - position); + + position += length; + } + } + + icy_string = icy_server_metadata_string(stream_title, ""); + + if (icy_string == NULL) + return NULL; + + Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1); + + g_free(icy_string); + + return icy_metadata; +} diff --git a/src/IcyMetaDataServer.hxx b/src/IcyMetaDataServer.hxx new file mode 100644 index 000000000..a37239cd7 --- /dev/null +++ b/src/IcyMetaDataServer.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ICY_META_DATA_SERVER_HXX +#define MPD_ICY_META_DATA_SERVER_HXX + +#include "tag/TagType.h" + +struct Tag; +class Page; + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint); + +Page * +icy_server_metadata_page(const Tag &tag, const enum tag_type *types); + +#endif diff --git a/src/IdTable.hxx b/src/IdTable.hxx new file mode 100644 index 000000000..fa93f1f38 --- /dev/null +++ b/src/IdTable.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ID_TABLE_HXX +#define MPD_ID_TABLE_HXX + +#include "gcc.h" + +#include <algorithm> + +#include <assert.h> + +/** + * A table that maps id numbers to position numbers. + */ +class IdTable { + unsigned size; + + unsigned next; + + int *data; + +public: + IdTable(unsigned _size):size(_size), next(1), data(new int[size]) { + std::fill_n(data, size, -1); + } + + ~IdTable() { + delete[] data; + } + + int IdToPosition(unsigned id) const { + return id < size + ? data[id] + : -1; + } + + unsigned GenerateId() { + assert(next > 0); + assert(next < size); + + while (true) { + unsigned id = next; + + ++next; + if (next == size) + next = 1; + + if (data[id] < 0) + return id; + } + } + + unsigned Insert(unsigned position) { + unsigned id = GenerateId(); + data[id] = position; + return id; + } + + void Move(unsigned id, unsigned position) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = position; + } + + void Erase(unsigned id) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = -1; + } +}; + +#endif diff --git a/src/Idle.cxx b/src/Idle.cxx new file mode 100644 index 000000000..840a5d4e5 --- /dev/null +++ b/src/Idle.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Support library for the "idle" command. + * + */ + +#include "config.h" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#include <atomic> + +#include <assert.h> + +static std::atomic_uint idle_flags; + +static const char *const idle_names[] = { + "database", + "stored_playlist", + "playlist", + "player", + "mixer", + "output", + "options", + "sticker", + "update", + "subscription", + "message", + nullptr +}; + +void +idle_add(unsigned flags) +{ + assert(flags != 0); + + unsigned old_flags = idle_flags.fetch_or(flags); + + if ((old_flags & flags) != flags) + GlobalEvents::Emit(GlobalEvents::IDLE); +} + +unsigned +idle_get(void) +{ + return idle_flags.exchange(0); +} + +const char*const* +idle_get_names(void) +{ + return idle_names; +} diff --git a/src/Idle.hxx b/src/Idle.hxx new file mode 100644 index 000000000..e78d5a12a --- /dev/null +++ b/src/Idle.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Support library for the "idle" command. + * + */ + +#ifndef MPD_IDLE_HXX +#define MPD_IDLE_HXX + +enum { + /** song database has been updated*/ + IDLE_DATABASE = 0x1, + + /** a stored playlist has been modified, created, deleted or + renamed */ + IDLE_STORED_PLAYLIST = 0x2, + + /** the current playlist has been modified */ + IDLE_PLAYLIST = 0x4, + + /** the player state has changed: play, stop, pause, seek, ... */ + IDLE_PLAYER = 0x8, + + /** the volume has been modified */ + IDLE_MIXER = 0x10, + + /** an audio output device has been enabled or disabled */ + IDLE_OUTPUT = 0x20, + + /** options have changed: crossfade, random, repeat, ... */ + IDLE_OPTIONS = 0x40, + + /** a sticker has been modified. */ + IDLE_STICKER = 0x80, + + /** a database update has started or finished. */ + IDLE_UPDATE = 0x100, + + /** a client has subscribed or unsubscribed to/from a channel */ + IDLE_SUBSCRIPTION = 0x200, + + /** a message on the subscribed channel was received */ + IDLE_MESSAGE = 0x400, +}; + +/** + * Adds idle flag (with bitwise "or") and queues notifications to all + * clients. + */ +void +idle_add(unsigned flags); + +/** + * Atomically reads and resets the global idle flags value. + */ +unsigned +idle_get(void); + +/** + * Get idle names + */ +const char*const* +idle_get_names(void); + +#endif diff --git a/src/InotifyDomain.cxx b/src/InotifyDomain.cxx new file mode 100644 index 000000000..1b8e62d38 --- /dev/null +++ b/src/InotifyDomain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InotifyDomain.hxx" +#include "util/Domain.hxx" + +const Domain inotify_domain("inotify"); diff --git a/src/InotifyDomain.hxx b/src/InotifyDomain.hxx new file mode 100644 index 000000000..005487804 --- /dev/null +++ b/src/InotifyDomain.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DOMAIN_HXX +#define MPD_INOTIFY_DOMAIN_HXX + +extern const class Domain inotify_domain; + +#endif diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx new file mode 100644 index 000000000..28b60ebee --- /dev/null +++ b/src/InotifyQueue.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InotifyQueue.hxx" +#include "InotifyDomain.hxx" +#include "UpdateGlue.hxx" +#include "event/Loop.hxx" +#include "Log.hxx" + +#include <string.h> + +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, +}; + +void +InotifyQueue::OnTimeout() +{ + unsigned id; + + while (!queue.empty()) { + const char *uri_utf8 = queue.front().c_str(); + + id = update_enqueue(uri_utf8, false); + if (id == 0) { + /* retry later */ + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); + return; + } + + FormatDebug(inotify_domain, "updating '%s' job=%u", + uri_utf8, id); + + queue.pop_front(); + } +} + +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 +InotifyQueue::Enqueue(const char *uri_utf8) +{ + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); + + for (auto i = queue.begin(), end = queue.end(); i != end;) { + const char *current_uri = i->c_str(); + + if (path_in(uri_utf8, current_uri)) + /* already enqueued */ + return; + + 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 */ + i = queue.erase(i); + else + ++i; + } + + queue.emplace_back(uri_utf8); +} diff --git a/src/InotifyQueue.hxx b/src/InotifyQueue.hxx new file mode 100644 index 000000000..818621ef8 --- /dev/null +++ b/src/InotifyQueue.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_INOTIFY_QUEUE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "gcc.h" + +#include <list> +#include <string> + +class InotifyQueue final : private TimeoutMonitor { + std::list<std::string> queue; + +public: + InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {} + + void Enqueue(const char *uri_utf8); + +private: + virtual void OnTimeout() override; +}; + +#endif diff --git a/src/InotifySource.cxx b/src/InotifySource.cxx new file mode 100644 index 000000000..587625d3d --- /dev/null +++ b/src/InotifySource.cxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InotifySource.hxx" +#include "InotifyDomain.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <sys/inotify.h> +#include <unistd.h> + +bool +InotifySource::OnSocketReady(gcc_unused unsigned flags) +{ + void *dest; + size_t length; + ssize_t nbytes; + + dest = fifo_buffer_write(buffer, &length); + if (dest == NULL) + FatalError("buffer full"); + + nbytes = read(Get(), dest, length); + if (nbytes < 0) + FatalSystemError("Failed to read from inotify"); + if (nbytes == 0) + FatalError("end of file from inotify"); + + fifo_buffer_append(buffer, nbytes); + + while (true) { + const char *name; + + const struct inotify_event *event = + (const struct inotify_event *) + fifo_buffer_read(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; + + callback(event->wd, event->mask, name, callback_ctx); + fifo_buffer_consume(buffer, sizeof(*event) + event->len); + } + + return true; +} + +inline +InotifySource::InotifySource(EventLoop &_loop, + mpd_inotify_callback_t _callback, void *_ctx, + int _fd) + :SocketMonitor(_fd, _loop), + callback(_callback), callback_ctx(_ctx), + buffer(fifo_buffer_new(4096)) +{ + ScheduleRead(); + +} + +InotifySource * +InotifySource::Create(EventLoop &loop, + mpd_inotify_callback_t callback, void *callback_ctx, + Error &error) +{ + int fd = inotify_init_cloexec(); + if (fd < 0) { + error.SetErrno("inotify_init() has failed"); + return NULL; + } + + return new InotifySource(loop, callback, callback_ctx, fd); +} + +InotifySource::~InotifySource() +{ + fifo_buffer_free(buffer); +} + +int +InotifySource::Add(const char *path_fs, unsigned mask, Error &error) +{ + int wd = inotify_add_watch(Get(), path_fs, mask); + if (wd < 0) + error.SetErrno("inotify_add_watch() has failed"); + + return wd; +} + +void +InotifySource::Remove(unsigned wd) +{ + int ret = inotify_rm_watch(Get(), wd); + if (ret < 0 && errno != EINVAL) + LogErrno(inotify_domain, "inotify_rm_watch() has failed"); + + /* EINVAL may happen here when the file has been deleted; the + kernel seems to auto-unregister deleted files */ +} diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx new file mode 100644 index 000000000..2f686d3b1 --- /dev/null +++ b/src/InotifySource.hxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_INOTIFY_SOURCE_HXX + +#include "event/SocketMonitor.hxx" +#include "gcc.h" + +class Error; + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +class InotifySource final : private SocketMonitor { + mpd_inotify_callback_t callback; + void *callback_ctx; + + struct fifo_buffer *buffer; + + InotifySource(EventLoop &_loop, + mpd_inotify_callback_t callback, void *ctx, int fd); + +public: + /** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ + static InotifySource *Create(EventLoop &_loop, + mpd_inotify_callback_t callback, + void *ctx, + Error &error); + + ~InotifySource(); + + + /** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ + int Add(const char *path_fs, unsigned mask, Error &error); + + /** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ + void Remove(unsigned wd); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/InotifyUpdate.cxx b/src/InotifyUpdate.cxx new file mode 100644 index 000000000..d65ff31f3 --- /dev/null +++ b/src/InotifyUpdate.cxx @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InotifyUpdate.hxx" +#include "InotifySource.hxx" +#include "InotifyQueue.hxx" +#include "InotifyDomain.hxx" +#include "Mapper.hxx" +#include "Main.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <map> +#include <forward_list> + +#include <assert.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <string.h> +#include <dirent.h> + +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 WatchDirectory { + WatchDirectory *parent; + + char *name; + + int descriptor; + + std::forward_list<WatchDirectory> children; + + WatchDirectory(WatchDirectory *_parent, const char *_name, + int _descriptor) + :parent(_parent), name(g_strdup(_name)), + descriptor(_descriptor) {} + + WatchDirectory(const WatchDirectory &) = delete; + WatchDirectory &operator=(const WatchDirectory &) = delete; + + ~WatchDirectory() { + g_free(name); + } +}; + +static InotifySource *inotify_source; +static InotifyQueue *inotify_queue; + +static unsigned inotify_max_depth; +static WatchDirectory *inotify_root; +static std::map<int, WatchDirectory *> inotify_directories; + +static void +tree_add_watch_directory(WatchDirectory *directory) +{ + inotify_directories.insert(std::make_pair(directory->descriptor, + directory)); +} + +static void +tree_remove_watch_directory(WatchDirectory *directory) +{ + auto i = inotify_directories.find(directory->descriptor); + assert(i != inotify_directories.end()); + inotify_directories.erase(i); +} + +static WatchDirectory * +tree_find_watch_directory(int wd) +{ + auto i = inotify_directories.find(wd); + if (i == inotify_directories.end()) + return nullptr; + + return i->second; +} + +static void +disable_watch_directory(WatchDirectory &directory) +{ + tree_remove_watch_directory(&directory); + + for (WatchDirectory &child : directory.children) + disable_watch_directory(child); + + inotify_source->Remove(directory.descriptor); +} + +static void +remove_watch_directory(WatchDirectory *directory) +{ + assert(directory != NULL); + + if (directory->parent == NULL) { + LogWarning(inotify_domain, + "music directory was removed - " + "cannot continue to watch it"); + return; + } + + disable_watch_directory(*directory); + + /* remove it from the parent, which effectively deletes it */ + directory->parent->children.remove_if([directory](const WatchDirectory &child){ + return &child == directory; + }); +} + +static char * +watch_directory_get_uri_fs(const WatchDirectory *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(WatchDirectory *directory, + const char *path_fs, unsigned depth) +{ + Error error; + 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) { + FormatErrno(inotify_domain, + "Failed to open directory %s", path_fs); + return; + } + + while ((ent = readdir(dir))) { + char *child_path_fs; + struct stat st; + int ret; + + 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) { + FormatErrno(inotify_domain, + "Failed to stat %s", + child_path_fs); + g_free(child_path_fs); + continue; + } + + if (!S_ISDIR(st.st_mode)) { + g_free(child_path_fs); + continue; + } + + ret = inotify_source->Add(child_path_fs, IN_MASK, error); + if (ret < 0) { + FormatError(error, + "Failed to register %s", child_path_fs); + error.Clear(); + g_free(child_path_fs); + continue; + } + + WatchDirectory *child = tree_find_watch_directory(ret); + if (child != NULL) { + /* already being watched */ + g_free(child_path_fs); + continue; + } + + directory->children.emplace_front(directory, ent->d_name, ret); + child = &directory->children.front(); + + tree_add_watch_directory(child); + + recursive_watch_subdirectories(child, child_path_fs, depth); + g_free(child_path_fs); + } + + closedir(dir); +} + +gcc_pure +static unsigned +watch_directory_depth(const WatchDirectory *d) +{ + assert(d != NULL); + + unsigned depth = 0; + while ((d = d->parent) != NULL) + ++depth; + + return depth; +} + +static void +mpd_inotify_callback(int wd, unsigned mask, + gcc_unused const char *name, gcc_unused void *ctx) +{ + WatchDirectory *directory; + char *uri_fs; + + /*FormatDebug(inotify_domain, "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 */ + const char *root = mapper_get_music_directory_fs().c_str(); + const char *path_fs; + char *allocated = NULL; + + if (uri_fs != NULL) + path_fs = allocated = + g_strconcat(root, "/", uri_fs, NULL); + else + path_fs = root; + + recursive_watch_subdirectories(directory, path_fs, + watch_directory_depth(directory)); + g_free(allocated); + } + + 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 */ + + if (uri_fs != nullptr) { + const std::string uri_utf8 = Path::ToUTF8(uri_fs); + if (!uri_utf8.empty()) + inotify_queue->Enqueue(uri_utf8.c_str()); + } + else + inotify_queue->Enqueue(""); + } + + g_free(uri_fs); +} + +void +mpd_inotify_init(unsigned max_depth) +{ + LogDebug(inotify_domain, "initializing inotify"); + + const Path &path = mapper_get_music_directory_fs(); + if (path.IsNull()) { + LogDebug(inotify_domain, "no music directory configured"); + return; + } + + Error error; + inotify_source = InotifySource::Create(*main_loop, + mpd_inotify_callback, nullptr, + error); + if (inotify_source == NULL) { + LogError(error); + return; + } + + inotify_max_depth = max_depth; + + int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error); + if (descriptor < 0) { + LogError(error); + delete inotify_source; + inotify_source = NULL; + return; + } + + inotify_root = new WatchDirectory(nullptr, path.c_str(), descriptor); + + tree_add_watch_directory(inotify_root); + + recursive_watch_subdirectories(inotify_root, path.c_str(), 0); + + inotify_queue = new InotifyQueue(*main_loop); + + LogDebug(inotify_domain, "watching music directory"); +} + +void +mpd_inotify_finish(void) +{ + if (inotify_source == NULL) + return; + + delete inotify_queue; + delete inotify_source; + delete inotify_root; + inotify_directories.clear(); +} diff --git a/src/InotifyUpdate.hxx b/src/InotifyUpdate.hxx new file mode 100644 index 000000000..44d0b10b7 --- /dev/null +++ b/src/InotifyUpdate.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_INOTIFY_UPDATE_HXX + +#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(gcc_unused unsigned max_depth) +{ +} + +static inline void +mpd_inotify_finish(void) +{ +} + +#endif /* !HAVE_INOTIFY_INIT */ + +#endif diff --git a/src/InputInit.cxx b/src/InputInit.cxx new file mode 100644 index 000000000..d39ca5325 --- /dev/null +++ b/src/InputInit.cxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InputInit.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "ConfigData.hxx" + +#include <assert.h> +#include <string.h> + +extern constexpr Domain input_domain("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, Error &error) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { + const char *name = param->GetBlockValue("plugin"); + if (name == NULL) { + error.Format(input_domain, + "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(Error &error) +{ + const config_param empty; + + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { + const struct input_plugin *plugin = input_plugins[i]; + + assert(plugin->name != NULL); + assert(*plugin->name != 0); + assert(plugin->open != NULL); + + const struct config_param *param = + input_plugin_config(plugin->name, error); + if (param == nullptr) { + if (error.IsDefined()) + return false; + + param = ∅ + } else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin->init == NULL || plugin->init(*param, error)) + input_plugins_enabled[i] = true; + else { + error.FormatPrefix("Failed to initialize input plugin '%s': ", + plugin->name); + return false; + } + } + + return true; +} + +void input_stream_global_finish(void) +{ + input_plugins_for_each_enabled(plugin) + if (plugin->finish != NULL) + plugin->finish(); +} diff --git a/src/InputInit.hxx b/src/InputInit.hxx new file mode 100644 index 000000000..9aa2de41a --- /dev/null +++ b/src/InputInit.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_INPUT_INIT_HXX + +class Error; + +/** + * Initializes this library and all input_stream implementations. + * + * @param error_r location to store the error occurring, or NULL to + * ignore errors + */ +bool +input_stream_global_init(Error &error); + +/** + * Deinitializes this library and all input_stream implementations. + */ +void input_stream_global_finish(void); + +#endif diff --git a/src/InputInternal.cxx b/src/InputInternal.cxx new file mode 100644 index 000000000..e110ebf0a --- /dev/null +++ b/src/InputInternal.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InputInternal.hxx" +#include "InputStream.hxx" + +void +input_stream_signal_client(struct input_stream *is) +{ + is->cond.broadcast(); +} + +void +input_stream_set_ready(struct input_stream *is) +{ + const ScopeLock protect(is->mutex); + + if (!is->ready) { + is->ready = true; + input_stream_signal_client(is); + } +} diff --git a/src/InputInternal.hxx b/src/InputInternal.hxx new file mode 100644 index 000000000..019ac09c6 --- /dev/null +++ b/src/InputInternal.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_INTERNAL_HXX +#define MPD_INPUT_INTERNAL_HXX + +#include "check.h" + +struct input_stream; + +void +input_stream_signal_client(struct input_stream *is); + +void +input_stream_set_ready(struct input_stream *is); + +#endif diff --git a/src/InputPlugin.hxx b/src/InputPlugin.hxx new file mode 100644 index 000000000..aa843d2c3 --- /dev/null +++ b/src/InputPlugin.hxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PLUGIN_HXX +#define MPD_INPUT_PLUGIN_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <glib.h> + +#include <stddef.h> + +struct config_param; +struct input_stream; +class Error; +struct Tag; + +struct input_plugin { + const char *name; + + /** + * Global initialization. This method is called when MPD starts. + * + * @param error_r location to store the error occurring, or + * NULL to ignore errors + * @return true on success, false if the plugin should be + * disabled + */ + bool (*init)(const config_param ¶m, Error &error); + + /** + * Global deinitialization. Called once before MPD shuts + * down (only if init() has returned true). + */ + void (*finish)(void); + + struct input_stream *(*open)(const char *uri, + Mutex &mutex, Cond &cond, + Error &error); + void (*close)(struct input_stream *is); + + /** + * Check for errors that may have occurred in the I/O thread. + * May be unimplemented for synchronous plugins. + * + * @return false on error + */ + bool (*check)(struct input_stream *is, Error &error); + + /** + * Update the public attributes. Call before access. Can be + * NULL if the plugin always keeps its attributes up to date. + */ + void (*update)(struct input_stream *is); + + Tag *(*tag)(struct input_stream *is); + + /** + * Returns true if the next read operation will not block: + * either data is available, or end-of-stream has been + * reached, or an error has occurred. + * + * If this method is unimplemented, then it is assumed that + * reading will never block. + */ + bool (*available)(struct input_stream *is); + + size_t (*read)(struct input_stream *is, void *ptr, size_t size, + Error &error); + bool (*eof)(struct input_stream *is); + bool (*seek)(struct input_stream *is, goffset offset, int whence, + Error &error); +}; + +#endif diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx new file mode 100644 index 000000000..75a106c24 --- /dev/null +++ b/src/InputRegistry.cxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "InputRegistry.hxx" +#include "input/FileInputPlugin.hxx" + +#ifdef ENABLE_ARCHIVE +#include "input/ArchiveInputPlugin.hxx" +#endif + +#ifdef ENABLE_CURL +#include "input/CurlInputPlugin.hxx" +#endif + +#ifdef HAVE_FFMPEG +#include "input/FfmpegInputPlugin.hxx" +#endif + +#ifdef ENABLE_MMS +#include "input/MmsInputPlugin.hxx" +#endif + +#ifdef ENABLE_CDIO_PARANOIA +#include "input/CdioParanoiaInputPlugin.hxx" +#endif + +#ifdef ENABLE_DESPOTIFY +#include "input/DespotifyInputPlugin.hxx" +#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 +#ifdef ENABLE_CDIO_PARANOIA + &input_plugin_cdio_paranoia, +#endif +#ifdef ENABLE_DESPOTIFY + &input_plugin_despotify, +#endif + NULL +}; + +bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/InputRegistry.hxx b/src/InputRegistry.hxx new file mode 100644 index 000000000..a080d108b --- /dev/null +++ b/src/InputRegistry.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_INPUT_REGISTRY_HXX + +#include "check.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[]; + +#define input_plugins_for_each(plugin) \ + for (const struct input_plugin *plugin, \ + *const*input_plugin_iterator = &input_plugins[0]; \ + (plugin = *input_plugin_iterator) != NULL; \ + ++input_plugin_iterator) + +#define input_plugins_for_each_enabled(plugin) \ + input_plugins_for_each(plugin) \ + if (input_plugins_enabled[input_plugin_iterator - input_plugins]) + +#endif diff --git a/src/InputStream.cxx b/src/InputStream.cxx new file mode 100644 index 000000000..8a9f6c66d --- /dev/null +++ b/src/InputStream.cxx @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InputStream.hxx" +#include "InputRegistry.hxx" +#include "InputPlugin.hxx" +#include "input/RewindInputPlugin.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> + +static constexpr Domain input_domain("input"); + +struct input_stream * +input_stream::Open(const char *url, + Mutex &mutex, Cond &cond, + Error &error) +{ + input_plugins_for_each_enabled(plugin) { + struct input_stream *is; + + is = plugin->open(url, mutex, cond, error); + if (is != NULL) { + assert(is->plugin.close != NULL); + assert(is->plugin.read != NULL); + assert(is->plugin.eof != NULL); + assert(!is->seekable || is->plugin.seek != NULL); + + is = input_rewind_open(is); + + return is; + } else if (error.IsDefined()) + return NULL; + } + + error.Set(input_domain, "Unrecognized URI"); + return NULL; +} + +bool +input_stream::Check(Error &error) +{ + return plugin.check == nullptr || plugin.check(this, error); +} + +void +input_stream::Update() +{ + if (plugin.update != nullptr) + plugin.update(this); +} + +void +input_stream::WaitReady() +{ + while (true) { + Update(); + if (ready) + break; + + cond.wait(mutex); + } +} + +void +input_stream::LockWaitReady() +{ + const ScopeLock protect(mutex); + WaitReady(); +} + +bool +input_stream::CheapSeeking() const +{ + return IsSeekable() && !uri_has_scheme(uri.c_str()); +} + +bool +input_stream::Seek(goffset _offset, int whence, Error &error) +{ + if (plugin.seek == nullptr) + return false; + + return plugin.seek(this, _offset, whence, error); +} + +bool +input_stream::LockSeek(goffset _offset, int whence, Error &error) +{ + if (plugin.seek == nullptr) + return false; + + const ScopeLock protect(mutex); + return Seek(_offset, whence, error); +} + +Tag * +input_stream::ReadTag() +{ + return plugin.tag != nullptr + ? plugin.tag(this) + : nullptr; +} + +Tag * +input_stream::LockReadTag() +{ + if (plugin.tag == nullptr) + return nullptr; + + const ScopeLock protect(mutex); + return ReadTag(); +} + +bool +input_stream::IsAvailable() +{ + return plugin.available != nullptr + ? plugin.available(this) + : true; +} + +size_t +input_stream::Read(void *ptr, size_t _size, Error &error) +{ + assert(ptr != NULL); + assert(_size > 0); + + return plugin.read(this, ptr, _size, error); +} + +size_t +input_stream::LockRead(void *ptr, size_t _size, Error &error) +{ + assert(ptr != NULL); + assert(_size > 0); + + const ScopeLock protect(mutex); + return Read(ptr, _size, error); +} + +void +input_stream::Close() +{ + plugin.close(this); +} + +bool +input_stream::IsEOF() +{ + return plugin.eof(this); +} + +bool +input_stream::LockIsEOF() +{ + const ScopeLock protect(mutex); + return IsEOF(); +} + diff --git a/src/InputStream.hxx b/src/InputStream.hxx new file mode 100644 index 000000000..eac7b0806 --- /dev/null +++ b/src/InputStream.hxx @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_STREAM_HXX +#define MPD_INPUT_STREAM_HXX + +#include "check.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "gcc.h" + +#include <string> + +#include <glib.h> + +#include <assert.h> + +class Error; +struct Tag; + +struct input_stream { + /** + * the plugin which implements this input stream + */ + const struct input_plugin &plugin; + + /** + * The absolute URI which was used to open this stream. + */ + std::string uri; + + /** + * A mutex that protects the mutable attributes of this object + * and its implementation. It must be locked before calling + * any of the public methods. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Mutex &mutex; + + /** + * A cond that gets signalled when the state of this object + * changes from the I/O thread. The client of this object may + * wait on it. Optional, may be NULL. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Cond &cond; + + /** + * indicates whether the stream is ready for reading and + * whether the other attributes in this struct are valid + */ + bool ready; + + /** + * if true, then the stream is fully seekable + */ + bool seekable; + + /** + * the size of the resource, or -1 if unknown + */ + goffset size; + + /** + * the current offset within the stream + */ + goffset offset; + + /** + * the MIME content type of the resource, or empty if unknown. + */ + std::string mime; + + input_stream(const input_plugin &_plugin, + const char *_uri, Mutex &_mutex, Cond &_cond) + :plugin(_plugin), uri(_uri), + mutex(_mutex), cond(_cond), + ready(false), seekable(false), + size(-1), offset(0) { + assert(_uri != NULL); + } + + /** + * Opens a new input stream. You may not access it until the "ready" + * flag is set. + * + * @param mutex a mutex that is used to protect this object; must be + * locked before calling any of the public methods + * @param cond a cond that gets signalled when the state of + * this object changes; may be NULL if the caller doesn't want to get + * notifications + * @return an #input_stream object on success, NULL on error + */ + gcc_nonnull_all + gcc_malloc + static input_stream *Open(const char *uri, Mutex &mutex, Cond &cond, + Error &error); + + /** + * Close the input stream and free resources. + * + * The caller must not lock the mutex. + */ + void Close(); + + void Lock() { + mutex.lock(); + } + + void Unlock() { + mutex.unlock(); + } + + /** + * Check for errors that may have occurred in the I/O thread. + * + * @return false on error + */ + bool Check(Error &error); + + /** + * Update the public attributes. Call before accessing attributes + * such as "ready" or "offset". + */ + void Update(); + + /** + * Wait until the stream becomes ready. + * + * The caller must lock the mutex. + */ + void WaitReady(); + + /** + * Wrapper for WaitReady() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ + void LockWaitReady(); + + gcc_pure + const char *GetMimeType() const { + assert(ready); + + return mime.empty() ? nullptr : mime.c_str(); + } + + gcc_nonnull_all + void OverrideMimeType(const char *_mime) { + assert(ready); + + mime = _mime; + } + + gcc_pure + goffset GetSize() const { + assert(ready); + + return size; + } + + gcc_pure + goffset GetOffset() const { + assert(ready); + + return offset; + } + + gcc_pure + bool IsSeekable() const { + assert(ready); + + return seekable; + } + + /** + * Determines whether seeking is cheap. This is true for local files. + */ + gcc_pure + bool CheapSeeking() const; + + /** + * Seeks to the specified position in the stream. This will most + * likely fail if the "seekable" flag is false. + * + * The caller must lock the mutex. + * + * @param offset the relative offset + * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END + */ + bool Seek(goffset offset, int whence, Error &error); + + /** + * Wrapper for Seek() which locks and unlocks the mutex; the + * caller must not be holding it already. + */ + bool LockSeek(goffset offset, int whence, Error &error); + + /** + * Returns true if the stream has reached end-of-file. + * + * The caller must lock the mutex. + */ + gcc_pure + bool IsEOF(); + + /** + * Wrapper for IsEOF() which locks and unlocks the mutex; the + * caller must not be holding it already. + */ + gcc_pure + bool LockIsEOF(); + + /** + * Reads the tag from the stream. + * + * The caller must lock the mutex. + * + * @return a tag object which must be freed by the caller, or + * nullptr if the tag has not changed since the last call + */ + gcc_malloc + Tag *ReadTag(); + + /** + * Wrapper for ReadTag() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ + gcc_malloc + Tag *LockReadTag(); + + /** + * Returns true if the next read operation will not block: either data + * is available, or end-of-stream has been reached, or an error has + * occurred. + * + * The caller must lock the mutex. + */ + gcc_pure + bool IsAvailable(); + + /** + * Reads data from the stream into the caller-supplied buffer. + * Returns 0 on error or eof (check with IsEOF()). + * + * The caller must lock the mutex. + * + * @param is the input_stream object + * @param ptr the buffer to read into + * @param size the maximum number of bytes to read + * @return the number of bytes read + */ + gcc_nonnull_all + size_t Read(void *ptr, size_t size, Error &error); + + /** + * Wrapper for Read() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ + gcc_nonnull_all + size_t LockRead(void *ptr, size_t size, Error &error); +}; + +#endif diff --git a/src/Instance.cxx b/src/Instance.cxx new file mode 100644 index 000000000..9982e5826 --- /dev/null +++ b/src/Instance.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Instance.hxx" +#include "Partition.hxx" +#include "Idle.hxx" + +void +Instance::DeleteSong(const Song &song) +{ + partition->DeleteSong(song); +} + +void +Instance::DatabaseModified() +{ + partition->playlist.FullIncrementVersions(); + idle_add(IDLE_DATABASE); +} + +void +Instance::TagModified() +{ + partition->playlist.TagChanged(); +} + +void +Instance::SyncWithPlayer() +{ + partition->playlist.SyncWithPlayer(partition->pc); +} diff --git a/src/Instance.hxx b/src/Instance.hxx new file mode 100644 index 000000000..896656b10 --- /dev/null +++ b/src/Instance.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_INSTANCE_HXX +#define MPD_INSTANCE_HXX + +#include "check.h" + +class ClientList; +struct Partition; +struct Song; + +struct Instance { + ClientList *client_list; + + Partition *partition; + + void DeleteSong(const Song &song); + + /** + * The database has been modified. Propagate the change to + * all subsystems. + */ + void DatabaseModified(); + + /** + * A tag in the play queue has been modified. Propagate the + * change to all subsystems. + */ + void TagModified(); + + /** + * Synchronize the player with the play queue. + */ + void SyncWithPlayer(); +}; + +#endif diff --git a/src/Listen.cxx b/src/Listen.cxx new file mode 100644 index 000000000..6438d3ed4 --- /dev/null +++ b/src/Listen.cxx @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Listen.hxx" +#include "Main.hxx" +#include "Instance.hxx" +#include "Client.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "event/ServerSocket.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Path.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string.h> +#include <assert.h> + +#ifdef ENABLE_SYSTEMD_DAEMON +#include <systemd/sd-daemon.h> +#endif + +static constexpr Domain listen_domain("listen"); + +#define DEFAULT_PORT 6600 + +class ClientListener final : public ServerSocket { +public: + ClientListener():ServerSocket(*main_loop) {} + +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) { + client_new(*main_loop, *instance->partition, + fd, &address, address_length, uid); + } +}; + +static ClientListener *listen_socket; +int listen_port; + +static bool +listen_add_config_param(unsigned int port, + const struct config_param *param, + Error &error_r) +{ + assert(param != NULL); + + if (0 == strcmp(param->value, "any")) { + return listen_socket->AddPort(port, error_r); + } else if (param->value[0] == '/' || param->value[0] == '~') { + Path path = config_parse_path(param, error_r); + return !path.IsNull() && listen_socket->AddPath(path.c_str(), error_r); + } else { + return listen_socket->AddHost(param->value, port, error_r); + } +} + +static bool +listen_systemd_activation(Error &error_r) +{ +#ifdef ENABLE_SYSTEMD_DAEMON + int n = sd_listen_fds(true); + if (n <= 0) { + if (n < 0) + FormatErrno(listen_domain, -n, + "sd_listen_fds() failed"); + return false; + } + + for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n; + i != end; ++i) + if (!listen_socket->AddFD(i, error_r)) + return false; + + return true; +#else + (void)error_r; + return false; +#endif +} + +bool +listen_global_init(Error &error) +{ + assert(main_loop != nullptr); + + int port = config_get_positive(CONF_PORT, DEFAULT_PORT); + const struct config_param *param = + config_get_next_param(CONF_BIND_TO_ADDRESS, NULL); + bool success; + + listen_socket = new ClientListener(); + + if (listen_systemd_activation(error)) + return true; + + if (error.IsDefined()) + return false; + + if (param != NULL) { + /* "bind_to_address" is configured, create listeners + for all values */ + + do { + if (!listen_add_config_param(port, param, error)) { + delete listen_socket; + error.FormatPrefix("Failed to listen on %s (line %i): ", + param->value, param->line); + return false; + } + + param = config_get_next_param(CONF_BIND_TO_ADDRESS, + param); + } while (param != NULL); + } else { + /* no "bind_to_address" configured, bind the + configured port on all interfaces */ + + success = listen_socket->AddPort(port, error); + if (!success) { + delete listen_socket; + error.FormatPrefix("Failed to listen on *:%d: ", port); + return false; + } + } + + if (!listen_socket->Open(error)) { + delete listen_socket; + return false; + } + + listen_port = port; + return true; +} + +void listen_global_finish(void) +{ + LogDebug(listen_domain, "listen_global_finish called"); + + assert(listen_socket != NULL); + + delete listen_socket; +} diff --git a/src/Listen.hxx b/src/Listen.hxx new file mode 100644 index 000000000..a6fdb2f1c --- /dev/null +++ b/src/Listen.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LISTEN_HXX +#define MPD_LISTEN_HXX + +class Error; + +extern int listen_port; + +bool +listen_global_init(Error &error); + +void listen_global_finish(void); + +#endif diff --git a/src/Log.cxx b/src/Log.cxx new file mode 100644 index 000000000..f36e68133 --- /dev/null +++ b/src/Log.cxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "LogV.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +static GLogLevelFlags +ToGLib(LogLevel level) +{ + switch (level) { + case LogLevel::DEBUG: + return G_LOG_LEVEL_DEBUG; + + case LogLevel::INFO: + return G_LOG_LEVEL_MESSAGE; + + case LogLevel::WARNING: + case LogLevel::ERROR: + return G_LOG_LEVEL_WARNING; + } + + assert(false); + gcc_unreachable(); +} + +void +Log(const Domain &domain, LogLevel level, const char *msg) +{ + g_log(domain.GetName(), ToGLib(level), "%s", msg); +} + +void +LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap) +{ + g_logv(domain.GetName(), ToGLib(level), fmt, ap); +} + +void +LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + LogFormatV(domain, level, fmt, ap); + va_end(ap); +} + +void +FormatDebug(const Domain &domain, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + LogFormatV(domain, LogLevel::DEBUG, fmt, ap); + va_end(ap); +} + +void +FormatInfo(const Domain &domain, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + LogFormatV(domain, LogLevel::INFO, fmt, ap); + va_end(ap); +} + +void +FormatWarning(const Domain &domain, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + LogFormatV(domain, LogLevel::WARNING, fmt, ap); + va_end(ap); +} + +void +FormatError(const Domain &domain, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + LogFormatV(domain, LogLevel::ERROR, fmt, ap); + va_end(ap); +} + +void +LogError(const Error &error) +{ + Log(error.GetDomain(), LogLevel::ERROR, error.GetMessage()); +} + +void +LogError(const Error &error, const char *msg) +{ + LogFormat(error.GetDomain(), LogLevel::ERROR, "%s: %s", + msg, error.GetMessage()); +} + +void +FormatError(const Error &error, const char *fmt, ...) +{ + char msg[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + LogError(error, msg); +} + +void +LogErrno(const Domain &domain, int e, const char *msg) +{ + LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, g_strerror(e)); +} + +void +LogErrno(const Domain &domain, const char *msg) +{ + LogErrno(domain, errno, msg); +} + +static void +FormatErrnoV(const Domain &domain, int e, const char *fmt, va_list ap) +{ + char msg[1024]; + vsnprintf(msg, sizeof(msg), fmt, ap); + + LogErrno(domain, e, msg); +} + +void +FormatErrno(const Domain &domain, int e, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + FormatErrnoV(domain, e, fmt, ap); + va_end(ap); +} + +void +FormatErrno(const Domain &domain, const char *fmt, ...) +{ + const int e = errno; + + va_list ap; + va_start(ap, fmt); + FormatErrnoV(domain, e, fmt, ap); + va_end(ap); +} diff --git a/src/Log.hxx b/src/Log.hxx new file mode 100644 index 000000000..719f1c448 --- /dev/null +++ b/src/Log.hxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LOG_HXX +#define MPD_LOG_HXX + +#include "gcc.h" + +#ifdef WIN32 +#include <windows.h> +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class Error; +class Domain; + +enum class LogLevel { + DEBUG, + INFO, + WARNING, + ERROR, +}; + +void +Log(const Domain &domain, LogLevel level, const char *msg); + +gcc_fprintf_ +void +LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...); + +static inline void +LogDebug(const Domain &domain, const char *msg) +{ + Log(domain, LogLevel::DEBUG, msg); +} + +gcc_fprintf +void +FormatDebug(const Domain &domain, const char *fmt, ...); + +static inline void +LogInfo(const Domain &domain, const char *msg) +{ + Log(domain, LogLevel::INFO, msg); +} + +gcc_fprintf +void +FormatInfo(const Domain &domain, const char *fmt, ...); + +static inline void +LogWarning(const Domain &domain, const char *msg) +{ + Log(domain, LogLevel::WARNING, msg); +} + +gcc_fprintf +void +FormatWarning(const Domain &domain, const char *fmt, ...); + +static inline void +LogError(const Domain &domain, const char *msg) +{ + Log(domain, LogLevel::ERROR, msg); +} + +gcc_fprintf +void +FormatError(const Domain &domain, const char *fmt, ...); + +void +LogError(const Error &error); + +void +LogError(const Error &error, const char *msg); + +gcc_fprintf +void +FormatError(const Error &error, const char *fmt, ...); + +void +LogErrno(const Domain &domain, int e, const char *msg); + +void +LogErrno(const Domain &domain, const char *msg); + +gcc_fprintf_ +void +FormatErrno(const Domain &domain, int e, const char *fmt, ...); + +gcc_fprintf +void +FormatErrno(const Domain &domain, const char *fmt, ...); + +#endif /* LOG_H */ diff --git a/src/LogInit.cxx b/src/LogInit.cxx new file mode 100644 index 000000000..d5e5c1d1f --- /dev/null +++ b/src/LogInit.cxx @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "LogInit.hxx" +#include "Log.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <glib.h> + +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO + +#define LOG_DATE_BUF_SIZE 16 +#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) + +static constexpr Domain log_domain("log"); + +static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE; + +static const char *log_charset; + +static bool stdout_mode = true; +static int out_fd; +static Path out_path = Path::Null(); + +static void redirect_logs(int fd) +{ + assert(fd >= 0); + if (dup2(fd, STDOUT_FILENO) < 0) + FatalSystemError("Failed to dup2 stdout"); + if (dup2(fd, STDERR_FILENO) < 0) + FatalSystemError("Failed to dup2 stderr"); +} + +static const char *log_date(void) +{ + static char buf[LOG_DATE_BUF_SIZE]; + time_t t = time(NULL); + strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); + return buf; +} + +/** + * Determines the length of the string excluding trailing whitespace + * characters. + */ +static int +chomp_length(const char *p) +{ + size_t length = strlen(p); + + while (length > 0 && g_ascii_isspace(p[length - 1])) + --length; + + return (int)length; +} + +static void +file_log_func(const gchar *domain, + GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + char *converted; + + if (log_level > log_threshold) + return; + + if (log_charset != NULL) { + converted = g_convert_with_fallback(message, -1, + log_charset, "utf-8", + NULL, NULL, NULL, NULL); + if (converted != NULL) + message = converted; + } else + converted = NULL; + + if (domain == nullptr) + domain = ""; + + fprintf(stderr, "%s%s%s%.*s\n", + stdout_mode ? "" : log_date(), + domain, *domain == 0 ? "" : ": ", + chomp_length(message), message); + + g_free(converted); +} + +static void +log_init_stdout(void) +{ + g_log_set_default_handler(file_log_func, NULL); +} + +static int +open_log_file(void) +{ + assert(!out_path.IsNull()); + + return OpenFile(out_path, O_CREAT | O_WRONLY | O_APPEND, 0666); +} + +static bool +log_init_file(unsigned line, Error &error) +{ + assert(!out_path.IsNull()); + + out_fd = open_log_file(); + if (out_fd < 0) { + const std::string out_path_utf8 = out_path.ToUTF8(); + error.FormatErrno("failed to open log file \"%s\" (config line %u)", + out_path_utf8.c_str(), line); + return false; + } + + g_log_set_default_handler(file_log_func, NULL); + return true; +} + +#ifdef HAVE_SYSLOG + +static int +glib_to_syslog_level(GLogLevelFlags log_level) +{ + switch (log_level & G_LOG_LEVEL_MASK) { + case G_LOG_LEVEL_ERROR: + case G_LOG_LEVEL_CRITICAL: + return LOG_ERR; + + case G_LOG_LEVEL_WARNING: + return LOG_WARNING; + + case G_LOG_LEVEL_MESSAGE: + return LOG_NOTICE; + + case G_LOG_LEVEL_INFO: + return LOG_INFO; + + case G_LOG_LEVEL_DEBUG: + return LOG_DEBUG; + + default: + return LOG_NOTICE; + } +} + +static void +syslog_log_func(const gchar *domain, + GLogLevelFlags log_level, const gchar *message, + gcc_unused gpointer user_data) +{ + if (stdout_mode) { + /* fall back to the file log function during + startup */ + file_log_func(domain, log_level, + message, user_data); + return; + } + + if (log_level > log_threshold) + return; + + if (domain == nullptr) + domain = ""; + + syslog(glib_to_syslog_level(log_level), "%s%s%.*s", + domain, *domain == 0 ? "" : ": ", + chomp_length(message), message); +} + +static void +log_init_syslog(void) +{ + assert(out_path.IsNull()); + + openlog(PACKAGE, 0, LOG_DAEMON); + g_log_set_default_handler(syslog_log_func, NULL); +} + +#endif + +static inline GLogLevelFlags +parse_log_level(const char *value, unsigned line) +{ + if (0 == strcmp(value, "default")) + return G_LOG_LEVEL_MESSAGE; + if (0 == strcmp(value, "secure")) + return LOG_LEVEL_SECURE; + else if (0 == strcmp(value, "verbose")) + return G_LOG_LEVEL_DEBUG; + else { + FormatFatalError("unknown log level \"%s\" at line %u", + value, line); + return G_LOG_LEVEL_MESSAGE; + } +} + +void +log_early_init(bool verbose) +{ + if (verbose) + log_threshold = G_LOG_LEVEL_DEBUG; + + log_init_stdout(); +} + +bool +log_init(bool verbose, bool use_stdout, Error &error) +{ + const struct config_param *param; + + g_get_charset(&log_charset); + + if (verbose) + log_threshold = G_LOG_LEVEL_DEBUG; + else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL) + log_threshold = parse_log_level(param->value, param->line); + + if (use_stdout) { + log_init_stdout(); + return true; + } else { + param = config_get_param(CONF_LOG_FILE); + if (param == NULL) { +#ifdef HAVE_SYSLOG + /* no configuration: default to syslog (if + available) */ + log_init_syslog(); + return true; +#else + error.Set(log_domain, + "config parameter 'log_file' not found"); + return false; +#endif +#ifdef HAVE_SYSLOG + } else if (strcmp(param->value, "syslog") == 0) { + log_init_syslog(); + return true; +#endif + } else { + out_path = config_get_path(CONF_LOG_FILE, error); + return !out_path.IsNull() && + log_init_file(param->line, error); + } + } +} + +static void +close_log_files(void) +{ + if (stdout_mode) + return; + +#ifdef HAVE_SYSLOG + if (out_path.IsNull()) + closelog(); +#endif +} + +void +log_deinit(void) +{ + close_log_files(); + out_path = Path::Null(); +} + + +void setup_log_output(bool use_stdout) +{ + fflush(NULL); + if (!use_stdout) { +#ifndef WIN32 + if (out_path.IsNull()) + out_fd = open("/dev/null", O_WRONLY); +#endif + + if (out_fd >= 0) { + redirect_logs(out_fd); + close(out_fd); + } + + stdout_mode = false; + log_charset = NULL; + } +} + +int cycle_log_files(void) +{ + int fd; + + if (stdout_mode || out_path.IsNull()) + return 0; + + assert(!out_path.IsNull()); + + FormatDebug(log_domain, "Cycling log files"); + close_log_files(); + + fd = open_log_file(); + if (fd < 0) { + const std::string out_path_utf8 = out_path.ToUTF8(); + FormatError(log_domain, + "error re-opening log file: %s", + out_path_utf8.c_str()); + return -1; + } + + redirect_logs(fd); + FormatDebug(log_domain, "Done cycling log files"); + return 0; +} diff --git a/src/LogInit.hxx b/src/LogInit.hxx new file mode 100644 index 000000000..2369fcdca --- /dev/null +++ b/src/LogInit.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LOG_INIT_HXX +#define MPD_LOG_INIT_HXX + +class Error; + +/** + * Configure a logging destination for daemon startup, before the + * configuration file is read. This allows the daemon to use the + * logging library (and the command line verbose level) before it's + * daemonized. + * + * @param verbose true when the program is started with --verbose + */ +void +log_early_init(bool verbose); + +bool +log_init(bool verbose, bool use_stdout, Error &error); + +void +log_deinit(void); + +void setup_log_output(bool use_stdout); + +int cycle_log_files(void); + +#endif /* LOG_H */ diff --git a/src/LogV.hxx b/src/LogV.hxx new file mode 100644 index 000000000..4bd4f801d --- /dev/null +++ b/src/LogV.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LOGV_HXX +#define MPD_LOGV_HXX + +#include "Log.hxx" + +#include <stdarg.h> + +void +LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap); + +#endif /* LOG_H */ diff --git a/src/Main.cxx b/src/Main.cxx new file mode 100644 index 000000000..8509abcfb --- /dev/null +++ b/src/Main.cxx @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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.hxx" +#include "Instance.hxx" +#include "CommandLine.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistGlobal.hxx" +#include "UpdateGlue.hxx" +#include "MusicChunk.hxx" +#include "StateFile.hxx" +#include "PlayerThread.hxx" +#include "Mapper.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "Permission.hxx" +#include "Listen.hxx" +#include "Client.hxx" +#include "ClientList.hxx" +#include "AllCommands.hxx" +#include "Partition.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "tag/TagConfig.hxx" +#include "ReplayGainConfig.hxx" +#include "Idle.hxx" +#include "SignalHandlers.hxx" +#include "Log.hxx" +#include "LogInit.hxx" +#include "GlobalEvents.hxx" +#include "InputInit.hxx" +#include "event/Loop.hxx" +#include "IOThread.hxx" +#include "fs/Path.hxx" +#include "PlaylistRegistry.hxx" +#include "ZeroconfGlue.hxx" +#include "DecoderList.hxx" +#include "AudioConfig.hxx" +#include "pcm/PcmResample.hxx" +#include "Daemon.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigData.hxx" +#include "ConfigDefaults.hxx" +#include "ConfigOption.hxx" +#include "Stats.hxx" + +#ifdef ENABLE_INOTIFY +#include "InotifyUpdate.hxx" +#endif + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#endif + +enum { + DEFAULT_BUFFER_SIZE = 2048, + DEFAULT_BUFFER_BEFORE_PLAY = 10, +}; + +static constexpr Domain main_domain("main"); + +GThread *main_task; +EventLoop *main_loop; + +Instance *instance; + +static StateFile *state_file; + +static bool +glue_daemonize_init(const struct options *options, Error &error) +{ + Path pid_file = config_get_path(CONF_PID_FILE, error); + if (pid_file.IsNull() && error.IsDefined()) + return false; + + daemonize_init(config_get_string(CONF_USER, nullptr), + config_get_string(CONF_GROUP, nullptr), + std::move(pid_file)); + + if (options->kill) + daemonize_kill(); + + return true; +} + +static bool +glue_mapper_init(Error &error) +{ + Path music_dir = config_get_path(CONF_MUSIC_DIR, error); + if (music_dir.IsNull() && error.IsDefined()) + return false; + + Path playlist_dir = config_get_path(CONF_PLAYLIST_DIR, error); + if (playlist_dir.IsNull() && error.IsDefined()) + return false; + + if (music_dir.IsNull()) { + music_dir = Path::FromUTF8(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC), + error); + if (music_dir.IsNull()) + return false; + } + + mapper_init(std::move(music_dir), std::move(playlist_dir)); + return true; +} + +/** + * Returns the database. If this function returns false, this has not + * succeeded, and the caller should create the database after the + * process has been daemonized. + */ +static bool +glue_db_init_and_load(void) +{ + const struct config_param *param = config_get_param(CONF_DATABASE); + const struct config_param *path = config_get_param(CONF_DB_FILE); + + if (param != nullptr && path != nullptr) + LogInfo(main_domain, + "Found both 'database' and 'db_file' setting - ignoring the latter"); + + if (!mapper_has_music_directory()) { + if (param != nullptr) + LogInfo(main_domain, + "Found database setting without " + "music_directory - disabling database"); + if (path != nullptr) + LogInfo(main_domain, + "Found db_file setting without " + "music_directory - disabling database"); + return true; + } + + struct config_param *allocated = nullptr; + + if (param == nullptr && path != nullptr) { + allocated = new config_param("database", path->line); + allocated->AddBlockParam("path", path->value, path->line); + param = allocated; + } + + if (param == nullptr) + return true; + + Error error; + if (!DatabaseGlobalInit(*param, error)) + FatalError(error); + + delete allocated; + + if (!DatabaseGlobalOpen(error)) + FatalError(error); + + /* run database update after daemonization? */ + return !db_is_simple() || db_exists(); +} + +/** + * Configure and initialize the sticker subsystem. + */ +static void +glue_sticker_init(void) +{ +#ifdef ENABLE_SQLITE + Error error; + Path sticker_file = config_get_path(CONF_STICKER_FILE, error); + if (sticker_file.IsNull() && error.IsDefined()) + FatalError(error); + + if (!sticker_global_init(std::move(sticker_file), error)) + FatalError(error); +#endif +} + +static bool +glue_state_file_init(Error &error) +{ + Path path_fs = config_get_path(CONF_STATE_FILE, error); + if (path_fs.IsNull()) + return !error.IsDefined(); + + state_file = new StateFile(std::move(path_fs), + *instance->partition, *main_loop); + state_file->Read(); + return true; +} + +/** + * Windows-only initialization of the Winsock2 library. + */ +static void winsock_init(void) +{ +#ifdef WIN32 + WSADATA sockinfo; + int retval; + + retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); + if(retval != 0) + FormatFatalError("Attempt to open Winsock2 failed; error code %d", + retval); + + if (LOBYTE(sockinfo.wVersion) != 2) + FatalError("We use Winsock2 but your version is either too new " + "or old; please install Winsock 2.x"); +#endif +} + +/** + * Initialize the decoder and player core, including the music pipe. + */ +static void +initialize_decoder_and_player(void) +{ + const struct config_param *param; + char *test; + size_t buffer_size; + float perc; + unsigned buffered_chunks; + unsigned buffered_before_play; + + param = config_get_param(CONF_AUDIO_BUFFER_SIZE); + if (param != nullptr) { + long tmp = strtol(param->value, &test, 10); + if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) + FormatFatalError("buffer size \"%s\" is not a " + "positive integer, line %i", + param->value, param->line); + buffer_size = tmp; + } else + buffer_size = DEFAULT_BUFFER_SIZE; + + buffer_size *= 1024; + + buffered_chunks = buffer_size / CHUNK_SIZE; + + if (buffered_chunks >= 1 << 15) + FormatFatalError("buffer size \"%lu\" is too big", + (unsigned long)buffer_size); + + param = config_get_param(CONF_BUFFER_BEFORE_PLAY); + if (param != nullptr) { + perc = strtod(param->value, &test); + if (*test != '%' || perc < 0 || perc > 100) { + FormatFatalError("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; + + buffered_before_play = (perc / 100) * buffered_chunks; + if (buffered_before_play > buffered_chunks) + buffered_before_play = buffered_chunks; + + const unsigned max_length = + config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH); + + instance->partition = new Partition(*instance, + max_length, + buffered_chunks, + buffered_before_play); +} + +/** + * Handler for GlobalEvents::IDLE. + */ +static void +idle_event_emitted(void) +{ + /* send "idle" notifications to all subscribed + clients */ + unsigned flags = idle_get(); + if (flags != 0) + instance->client_list->IdleAdd(flags); + + if (flags & (IDLE_PLAYLIST|IDLE_PLAYER|IDLE_MIXER|IDLE_OUTPUT) && + state_file != nullptr) + state_file->CheckModified(); +} + +#ifdef WIN32 + +/** + * Handler for GlobalEvents::SHUTDOWN. + */ +static void +shutdown_event_emitted(void) +{ + main_loop->Break(); +} + +#endif + +int main(int argc, char *argv[]) +{ +#ifdef WIN32 + return win32_main(argc, argv); +#else + return mpd_main(argc, argv); +#endif +} + +int mpd_main(int argc, char *argv[]) +{ + struct options options; + clock_t start; + bool create_db; + Error error; + bool success; + + daemonize_close_stdin(); + +#ifdef HAVE_LOCALE_H + /* initialize locale */ + setlocale(LC_CTYPE,""); +#endif + + g_set_application_name("Music Player Daemon"); + +#if !GLIB_CHECK_VERSION(2,32,0) + /* enable GLib's thread safety code */ + g_thread_init(nullptr); +#endif + + io_thread_init(); + winsock_init(); + config_global_init(); + + success = parse_cmdline(argc, argv, &options, error); + if (!success) { + LogError(error); + return EXIT_FAILURE; + } + + if (!glue_daemonize_init(&options, error)) { + LogError(error); + return EXIT_FAILURE; + } + + stats_global_init(); + TagLoadConfig(); + + if (!log_init(options.verbose, options.log_stderr, error)) { + LogError(error); + return EXIT_FAILURE; + } + + main_task = g_thread_self(); + main_loop = new EventLoop(EventLoop::Default()); + + instance = new Instance(); + + const unsigned max_clients = config_get_positive(CONF_MAX_CONN, 10); + instance->client_list = new ClientList(max_clients); + + success = listen_global_init(error); + if (!success) { + LogError(error); + return EXIT_FAILURE; + } + + daemonize_set_user(); + + GlobalEvents::Initialize(*main_loop); + GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); +#ifdef WIN32 + GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); +#endif + + Path::GlobalInit(); + + if (!glue_mapper_init(error)) { + LogError(error); + return EXIT_FAILURE; + } + + initPermissions(); + playlist_global_init(); + spl_global_init(); +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + if (!pcm_resample_global_init(error)) { + LogError(error); + return EXIT_FAILURE; + } + + decoder_plugin_init_all(); + update_global_init(); + + create_db = !glue_db_init_and_load(); + + glue_sticker_init(); + + command_init(); + initialize_decoder_and_player(); + volume_init(); + initAudioConfig(); + audio_output_all_init(&instance->partition->pc); + client_manager_init(); + replay_gain_global_init(); + + if (!input_stream_global_init(error)) { + LogError(error); + return EXIT_FAILURE; + } + + playlist_list_global_init(); + + daemonize(options.daemon); + + setup_log_output(options.log_stderr); + + SignalHandlersInit(*main_loop); + + io_thread_start(); + + ZeroconfInit(*main_loop); + + player_create(instance->partition->pc); + + if (create_db) { + /* the database failed to load: recreate the + database */ + unsigned job = update_enqueue(nullptr, true); + if (job == 0) + FatalError("directory update failed"); + } + + if (!glue_state_file_init(error)) { + g_printerr("%s\n", error.GetMessage()); + return EXIT_FAILURE; + } + + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); + + 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) + FormatWarning(main_domain, + "inotify: auto_update was disabled. enable during compilation phase"); +#endif + + config_global_check(); + + /* enable all audio outputs (if not already done by + playlist_state_restore() */ + instance->partition->pc.UpdateAudio(); + +#ifdef WIN32 + win32_app_started(); +#endif + + /* run the main loop */ + main_loop->Run(); + +#ifdef WIN32 + win32_app_stopping(); +#endif + + /* cleanup */ + +#ifdef ENABLE_INOTIFY + mpd_inotify_finish(); +#endif + + if (state_file != nullptr) { + state_file->Write(); + delete state_file; + } + + instance->partition->pc.Kill(); + ZeroconfDeinit(); + listen_global_finish(); + delete instance->client_list; + + start = clock(); + DatabaseGlobalDeinit(); + FormatDebug(main_domain, + "db_finish took %f seconds", + ((float)(clock()-start))/CLOCKS_PER_SEC); + +#ifdef ENABLE_SQLITE + sticker_global_finish(); +#endif + + GlobalEvents::Deinitialize(); + + playlist_list_global_finish(); + input_stream_global_finish(); + audio_output_all_finish(); + volume_finish(); + mapper_finish(); + delete instance->partition; + command_finish(); + update_global_finish(); + decoder_plugin_deinit_all(); +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + config_global_finish(); + stats_global_finish(); + io_thread_deinit(); + SignalHandlersFinish(); + delete instance; + delete main_loop; + daemonize_finish(); +#ifdef WIN32 + WSACleanup(); +#endif + + log_deinit(); + return EXIT_SUCCESS; +} diff --git a/src/Main.hxx b/src/Main.hxx new file mode 100644 index 000000000..a789300ed --- /dev/null +++ b/src/Main.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MAIN_HXX +#define MPD_MAIN_HXX + +class EventLoop; +struct Instance; +typedef struct _GThread GThread; + +extern GThread *main_task; + +extern EventLoop *main_loop; + +extern Instance *instance; + +/** + * 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 GlobalEvents::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/Mapper.cxx b/src/Mapper.cxx new file mode 100644 index 000000000..e2d082122 --- /dev/null +++ b/src/Mapper.cxx @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Maps directory and song objects to file system paths. + */ + +#include "config.h" +#include "Mapper.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "fs/DirectoryReader.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> + +static constexpr Domain mapper_domain("mapper"); + +/** + * The absolute path of the music directory encoded in UTF-8. + */ +static char *music_dir_utf8; +static size_t music_dir_utf8_length; + +/** + * The absolute path of the music directory encoded in the filesystem + * character set. + */ +static Path music_dir_fs = Path::Null(); +static size_t music_dir_fs_length; + +/** + * The absolute path of the playlist directory encoded in the + * filesystem character set. + */ +static Path playlist_dir_fs = Path::Null(); + +/** + * Duplicate a string, chop all trailing slashes. + */ +static char * +strdup_chop_slash(const char *path_fs) +{ + size_t length = strlen(path_fs); + + while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR) + --length; + + return g_strndup(path_fs, length); +} + +static void +check_directory(const char *path_utf8, const Path &path_fs) +{ + struct stat st; + if (!StatFile(path_fs, st)) { + FormatErrno(mapper_domain, + "Failed to stat directory \"%s\"", + path_utf8); + return; + } + + if (!S_ISDIR(st.st_mode)) { + FormatError(mapper_domain, + "Not a directory: %s", path_utf8); + return; + } + +#ifndef WIN32 + const Path x = Path::Build(path_fs, "."); + if (!StatFile(x, st) && errno == EACCES) + FormatError(mapper_domain, + "No permission to traverse (\"execute\") directory: %s", + path_utf8); +#endif + + const DirectoryReader reader(path_fs); + if (reader.HasFailed() && errno == EACCES) + FormatError(mapper_domain, + "No permission to read directory: %s", path_utf8); +} + +static void +mapper_set_music_dir(Path &&path) +{ + assert(!path.IsNull()); + + music_dir_fs = std::move(path); + music_dir_fs_length = music_dir_fs.length(); + + const auto utf8 = music_dir_fs.ToUTF8(); + music_dir_utf8 = strdup_chop_slash(utf8.c_str()); + music_dir_utf8_length = strlen(music_dir_utf8); + + check_directory(music_dir_utf8, music_dir_fs); +} + +static void +mapper_set_playlist_dir(Path &&path) +{ + assert(!path.IsNull()); + + playlist_dir_fs = std::move(path); + + const auto utf8 = playlist_dir_fs.ToUTF8(); + check_directory(utf8.c_str(), playlist_dir_fs); +} + +void +mapper_init(Path &&_music_dir, Path &&_playlist_dir) +{ + if (!_music_dir.IsNull()) + mapper_set_music_dir(std::move(_music_dir)); + + if (!_playlist_dir.IsNull()) + mapper_set_playlist_dir(std::move(_playlist_dir)); +} + +void mapper_finish(void) +{ + g_free(music_dir_utf8); +} + +const char * +mapper_get_music_directory_utf8(void) +{ + return music_dir_utf8; +} + +const Path & +mapper_get_music_directory_fs(void) +{ + return music_dir_fs; +} + +const char * +map_to_relative_path(const char *path_utf8) +{ + return music_dir_utf8 != NULL && + memcmp(path_utf8, music_dir_utf8, + music_dir_utf8_length) == 0 && + G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length]) + ? path_utf8 + music_dir_utf8_length + 1 + : path_utf8; +} + +Path +map_uri_fs(const char *uri) +{ + assert(uri != NULL); + assert(*uri != '/'); + + if (music_dir_fs.IsNull()) + return Path::Null(); + + const Path uri_fs = Path::FromUTF8(uri); + if (uri_fs.IsNull()) + return Path::Null(); + + return Path::Build(music_dir_fs, uri_fs); +} + +Path +map_directory_fs(const Directory *directory) +{ + assert(music_dir_utf8 != NULL); + assert(!music_dir_fs.IsNull()); + + if (directory->IsRoot()) + return music_dir_fs; + + return map_uri_fs(directory->GetPath()); +} + +Path +map_directory_child_fs(const Directory *directory, const char *name) +{ + assert(music_dir_utf8 != NULL); + assert(!music_dir_fs.IsNull()); + + /* check for invalid or unauthorized base names */ + if (*name == 0 || strchr(name, '/') != NULL || + strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + return Path::Null(); + + const Path parent_fs = map_directory_fs(directory); + if (parent_fs.IsNull()) + return Path::Null(); + + const Path name_fs = Path::FromUTF8(name); + if (name_fs.IsNull()) + return Path::Null(); + + return Path::Build(parent_fs, name_fs); +} + +/** + * Map a song object that was created by song_dup_detached(). It does + * not have a real parent directory, only the dummy object + * #detached_root. + */ +static Path +map_detached_song_fs(const char *uri_utf8) +{ + Path uri_fs = Path::FromUTF8(uri_utf8); + if (uri_fs.IsNull()) + return Path::Null(); + + return Path::Build(music_dir_fs, uri_fs); +} + +Path +map_song_fs(const Song *song) +{ + assert(song->IsFile()); + + if (song->IsInDatabase()) + return song->IsDetached() + ? map_detached_song_fs(song->uri) + : map_directory_child_fs(song->parent, song->uri); + else + return Path::FromUTF8(song->uri); +} + +char * +map_fs_to_utf8(const char *path_fs) +{ + if (!music_dir_fs.IsNull() && + strncmp(path_fs, music_dir_fs.c_str(), music_dir_fs_length) == 0 && + G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length])) + /* remove musicDir prefix */ + path_fs += music_dir_fs_length + 1; + else if (G_IS_DIR_SEPARATOR(path_fs[0])) + /* not within musicDir */ + return NULL; + + while (path_fs[0] == G_DIR_SEPARATOR) + ++path_fs; + + const std::string path_utf8 = Path::ToUTF8(path_fs); + if (path_utf8.empty()) + return nullptr; + + return g_strdup(path_utf8.c_str()); +} + +const Path & +map_spl_path(void) +{ + return playlist_dir_fs; +} + +Path +map_spl_utf8_to_fs(const char *name) +{ + if (playlist_dir_fs.IsNull()) + return Path::Null(); + + char *filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL); + const Path filename_fs = Path::FromUTF8(filename_utf8); + g_free(filename_utf8); + if (filename_fs.IsNull()) + return Path::Null(); + + return Path::Build(playlist_dir_fs, filename_fs); +} diff --git a/src/Mapper.hxx b/src/Mapper.hxx new file mode 100644 index 000000000..a224b8051 --- /dev/null +++ b/src/Mapper.hxx @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Maps directory and song objects to file system paths. + */ + +#ifndef MPD_MAPPER_HXX +#define MPD_MAPPER_HXX + +#include "gcc.h" + +#define PLAYLIST_FILE_SUFFIX ".m3u" + +class Path; +struct Directory; +struct Song; + +void +mapper_init(Path &&music_dir, Path &&playlist_dir); + +void mapper_finish(void); + +/** + * Return the absolute path of the music directory encoded in UTF-8. + */ +gcc_const +const char * +mapper_get_music_directory_utf8(void); + +/** + * Return the absolute path of the music directory encoded in the + * filesystem character set. + */ +gcc_const +const Path & +mapper_get_music_directory_fs(void); + +/** + * Returns true if a music directory was configured. + */ +gcc_const +static inline bool +mapper_has_music_directory(void) +{ + return mapper_get_music_directory_utf8() != nullptr; +} + +/** + * 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. + */ +gcc_pure +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. + */ +gcc_pure +Path +map_uri_fs(const char *uri); + +/** + * Determines the file system path of a directory object. + * + * @param directory the directory object + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_directory_fs(const Directory *directory); + +/** + * Determines the file system path of a directory's child (may be a + * sub directory or a song). + * + * @param directory the parent directory object + * @param name the child's name in UTF-8 + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_directory_child_fs(const Directory *directory, const char *name); + +/** + * Determines the file system path of a song. This must not be a + * remote song. + * + * @param song the song object + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_song_fs(const Song *song); + +/** + * Maps a file system path (relative to the music directory or + * absolute) to a relative path in UTF-8 encoding. + * + * @param path_fs a path in file system encoding + * @return the relative path in UTF-8, or nullptr if mapping failed + */ +gcc_malloc +char * +map_fs_to_utf8(const char *path_fs); + +/** + * Returns the playlist directory. + */ +gcc_const +const Path & +map_spl_path(void); + +/** + * Maps a playlist name (without the ".m3u" suffix) to a file system + * path. The return value is allocated on the heap and must be freed + * with g_free(). + * + * @return the path in file system encoding, or nullptr if mapping failed + */ +gcc_pure +Path +map_spl_utf8_to_fs(const char *name); + +#endif diff --git a/src/MemorySongEnumerator.cxx b/src/MemorySongEnumerator.cxx new file mode 100644 index 000000000..7c9d05daa --- /dev/null +++ b/src/MemorySongEnumerator.cxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MemorySongEnumerator.hxx" + +Song * +MemorySongEnumerator::NextSong() +{ + if (songs.empty()) + return nullptr; + + auto result = songs.front().Steal(); + songs.pop_front(); + return result; +} diff --git a/src/MemorySongEnumerator.hxx b/src/MemorySongEnumerator.hxx new file mode 100644 index 000000000..46086a064 --- /dev/null +++ b/src/MemorySongEnumerator.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MEMORY_PLAYLIST_PROVIDER_HXX +#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX + +#include "SongEnumerator.hxx" +#include "SongPointer.hxx" + +#include <forward_list> + +class MemorySongEnumerator final : public SongEnumerator { + std::forward_list<SongPointer> songs; + +public: + MemorySongEnumerator(std::forward_list<SongPointer> &&_songs) + :songs(std::move(_songs)) {} + + virtual Song *NextSong() override; +}; + +#endif diff --git a/src/MessageCommands.cxx b/src/MessageCommands.cxx new file mode 100644 index 000000000..2971b4cde --- /dev/null +++ b/src/MessageCommands.cxx @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MessageCommands.hxx" +#include "ClientSubscribe.hxx" +#include "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Instance.hxx" +#include "Main.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include <set> +#include <string> + +#include <assert.h> + +enum command_return +handle_subscribe(Client *client, gcc_unused int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +enum command_return +handle_unsubscribe(Client *client, gcc_unused int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_channels(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + assert(argc == 1); + + std::set<std::string> channels; + for (const auto &c : *instance->client_list) + channels.insert(c->subscriptions.begin(), + c->subscriptions.end()); + + for (const auto &channel : channels) + client_printf(client, "channel: %s\n", channel.c_str()); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_read_messages(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + assert(argc == 1); + + while (!client->messages.empty()) { + const ClientMessage &msg = client->messages.front(); + + client_printf(client, "channel: %s\nmessage: %s\n", + msg.GetChannel(), msg.GetMessage()); + client->messages.pop_front(); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_send_message(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + bool sent = false; + const ClientMessage msg(argv[1], argv[2]); + for (const auto &c : *instance->client_list) + if (client_push_message(c, msg)) + sent = true; + + if (sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} diff --git a/src/MessageCommands.hxx b/src/MessageCommands.hxx new file mode 100644 index 000000000..b10f3d8e8 --- /dev/null +++ b/src/MessageCommands.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MESSAGE_COMMANDS_HXX +#define MPD_MESSAGE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_subscribe(Client *client, int argc, char *argv[]); + +enum command_return +handle_unsubscribe(Client *client, int argc, char *argv[]); + +enum command_return +handle_channels(Client *client, int argc, char *argv[]); + +enum command_return +handle_read_messages(Client *client, int argc, char *argv[]); + +enum command_return +handle_send_message(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx new file mode 100644 index 000000000..e9994e901 --- /dev/null +++ b/src/MixerAll.cxx @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MixerAll.hxx" +#include "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "MixerList.hxx" +#include "OutputAll.hxx" +#include "pcm/PcmVolume.hxx" +#include "OutputInternal.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +static constexpr Domain mixer_domain("mixer"); + +static int +output_mixer_get_volume(unsigned i) +{ + struct audio_output *output; + int volume; + + assert(i < audio_output_count()); + + output = audio_output_get(i); + if (!output->enabled) + return -1; + + Mixer *mixer = output->mixer; + if (mixer == NULL) + return -1; + + Error error; + volume = mixer_get_volume(mixer, error); + if (volume < 0 && error.IsDefined()) + FormatError(error, + "Failed to read mixer for '%s'", + output->name); + + return volume; +} + +int +mixer_all_get_volume(void) +{ + unsigned count = audio_output_count(), ok = 0; + int volume, total = 0; + + for (unsigned i = 0; i < count; i++) { + volume = output_mixer_get_volume(i); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +static bool +output_mixer_set_volume(unsigned i, unsigned volume) +{ + struct audio_output *output; + bool success; + + assert(i < audio_output_count()); + assert(volume <= 100); + + output = audio_output_get(i); + if (!output->enabled) + return false; + + Mixer *mixer = output->mixer; + if (mixer == NULL) + return false; + + Error error; + success = mixer_set_volume(mixer, volume, error); + if (!success && error.IsDefined()) + FormatError(error, + "Failed to set mixer for '%s'", + output->name); + + return success; +} + +bool +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) + || success; + + return success; +} + +static int +output_mixer_get_software_volume(unsigned i) +{ + struct audio_output *output; + + assert(i < audio_output_count()); + + output = audio_output_get(i); + if (!output->enabled) + return -1; + + Mixer *mixer = output->mixer; + if (mixer == NULL || !mixer->IsPlugin(software_mixer_plugin)) + return -1; + + return mixer_get_volume(mixer, IgnoreError()); +} + +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, IgnoreError()); + } +} diff --git a/src/MixerAll.hxx b/src/MixerAll.hxx new file mode 100644 index 000000000..23350a843 --- /dev/null +++ b/src/MixerAll.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * Functions which affect the mixers of all audio outputs. + */ + +#ifndef MPD_MIXER_ALL_HXX +#define MPD_MIXER_ALL_HXX + +/** + * Returns the average volume of all available mixers (range 0..100). + * Returns -1 if no mixer can be queried. + */ +int +mixer_all_get_volume(void); + +/** + * Sets the volume on all available mixers. + * + * @param volume the volume (range 0..100) + * @return true on success, false on failure + */ +bool +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/MixerControl.cxx b/src/MixerControl.cxx new file mode 100644 index 000000000..2759f945a --- /dev/null +++ b/src/MixerControl.cxx @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stddef.h> + +Mixer * +mixer_new(const struct mixer_plugin *plugin, void *ao, + const config_param ¶m, + Error &error) +{ + Mixer *mixer; + + assert(plugin != NULL); + + mixer = plugin->init(ao, param, error); + + assert(mixer == NULL || mixer->IsPlugin(*plugin)); + + return mixer; +} + +void +mixer_free(Mixer *mixer) +{ + assert(mixer != NULL); + assert(mixer->plugin != NULL); + + /* mixers with the "global" flag set might still be open at + this point (see mixer_auto_close()) */ + mixer_close(mixer); + + mixer->plugin->finish(mixer); +} + +bool +mixer_open(Mixer *mixer, Error &error) +{ + bool success; + + assert(mixer != NULL); + assert(mixer->plugin != NULL); + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) + success = true; + else if (mixer->plugin->open == NULL) + success = mixer->open = true; + else + success = mixer->open = mixer->plugin->open(mixer, error); + + mixer->failed = !success; + + return success; +} + +static void +mixer_close_internal(Mixer *mixer) +{ + assert(mixer != NULL); + assert(mixer->plugin != NULL); + assert(mixer->open); + + if (mixer->plugin->close != NULL) + mixer->plugin->close(mixer); + + mixer->open = false; +} + +void +mixer_close(Mixer *mixer) +{ + assert(mixer != NULL); + assert(mixer->plugin != NULL); + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) + mixer_close_internal(mixer); +} + +void +mixer_auto_close(Mixer *mixer) +{ + if (!mixer->plugin->global) + mixer_close(mixer); +} + +/* + * Close the mixer due to failure. The mutex must be locked before + * calling this function. + */ +static void +mixer_failed(Mixer *mixer) +{ + assert(mixer->open); + + mixer_close_internal(mixer); + + mixer->failed = true; +} + +int +mixer_get_volume(Mixer *mixer, Error &error) +{ + int volume; + + assert(mixer != NULL); + + if (mixer->plugin->global && !mixer->failed && + !mixer_open(mixer, error)) + return -1; + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) { + volume = mixer->plugin->get_volume(mixer, error); + if (volume < 0 && error.IsDefined()) + mixer_failed(mixer); + } else + volume = -1; + + return volume; +} + +bool +mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + assert(mixer != NULL); + assert(volume <= 100); + + if (mixer->plugin->global && !mixer->failed && + !mixer_open(mixer, error)) + return false; + + const ScopeLock protect(mixer->mutex); + + return mixer->open && + mixer->plugin->set_volume(mixer, volume, error); +} diff --git a/src/MixerControl.hxx b/src/MixerControl.hxx new file mode 100644 index 000000000..c1ee01eec --- /dev/null +++ b/src/MixerControl.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * Functions which manipulate a #mixer object. + */ + +#ifndef MPD_MIXER_CONTROL_HXX +#define MPD_MIXER_CONTROL_HXX + +class Error; +class Mixer; +struct mixer_plugin; +struct config_param; + +Mixer * +mixer_new(const struct mixer_plugin *plugin, void *ao, + const config_param ¶m, + Error &error); + +void +mixer_free(Mixer *mixer); + +bool +mixer_open(Mixer *mixer, Error &error); + +void +mixer_close(Mixer *mixer); + +/** + * Close the mixer unless the plugin's "global" flag is set. This is + * called when the #audio_output is closed. + */ +void +mixer_auto_close(Mixer *mixer); + +int +mixer_get_volume(Mixer *mixer, Error &error); + +bool +mixer_set_volume(Mixer *mixer, unsigned volume, Error &error); + +#endif diff --git a/src/MixerInternal.hxx b/src/MixerInternal.hxx new file mode 100644 index 000000000..e421a34b4 --- /dev/null +++ b/src/MixerInternal.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_INTERNAL_HXX +#define MPD_MIXER_INTERNAL_HXX + +#include "MixerPlugin.hxx" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" + +class Mixer { +public: + const struct mixer_plugin *plugin; + + /** + * This mutex protects all of the mixer struct, including its + * implementation, so plugins don't have to deal with that. + */ + Mutex mutex; + + /** + * Is the mixer device currently open? + */ + bool open; + + /** + * Has this mixer failed, and should not be reopened + * automatically? + */ + bool failed; + +public: + Mixer(const mixer_plugin &_plugin) + :plugin(&_plugin), + open(false), + failed(false) {} + + bool IsPlugin(const mixer_plugin &other) const { + return plugin == &other; + } +}; + +#endif diff --git a/src/MixerList.hxx b/src/MixerList.hxx new file mode 100644 index 000000000..440f442ba --- /dev/null +++ b/src/MixerList.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 provides "extern" declarations for all mixer plugins. + */ + +#ifndef MPD_MIXER_LIST_HXX +#define MPD_MIXER_LIST_HXX + +extern const struct mixer_plugin software_mixer_plugin; +extern const struct mixer_plugin alsa_mixer_plugin; +extern const struct mixer_plugin oss_mixer_plugin; +extern const struct mixer_plugin roar_mixer_plugin; +extern const struct mixer_plugin pulse_mixer_plugin; +extern const struct mixer_plugin winmm_mixer_plugin; + +#endif diff --git a/src/MixerPlugin.hxx b/src/MixerPlugin.hxx new file mode 100644 index 000000000..4dd8969b9 --- /dev/null +++ b/src/MixerPlugin.hxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 mixer_plugin class. It should not be + * included directly; use MixerInternal.hxx instead in mixer + * implementations. + */ + +#ifndef MPD_MIXER_PLUGIN_HXX +#define MPD_MIXER_PLUGIN_HXX + +struct config_param; +class Mixer; +class Error; + +struct mixer_plugin { + /** + * Alocates and configures a mixer device. + * + * @param ao the pointer returned by audio_output_plugin.init + * @param param the configuration section + * @param error_r location to store the error occurring, or + * NULL to ignore errors + * @return a mixer object, or NULL on error + */ + Mixer *(*init)(void *ao, const config_param ¶m, + Error &error); + + /** + * Finish and free mixer data + */ + void (*finish)(Mixer *data); + + /** + * Open mixer device + * + * @param error_r location to store the error occurring, or + * NULL to ignore errors + * @return true on success, false on error + */ + bool (*open)(Mixer *data, Error &error); + + /** + * Close mixer device + */ + void (*close)(Mixer *data); + + /** + * Reads the current volume. + * + * @param error_r location to store the error occurring, or + * NULL to ignore errors + * @return the current volume (0..100 including) or -1 if + * unavailable or on error (error set, mixer will be closed) + */ + int (*get_volume)(Mixer *mixer, Error &error); + + /** + * Sets the volume. + * + * @param error_r location to store the error occurring, or + * NULL to ignore errors + * @param volume the new volume (0..100 including) + * @return true on success, false on error + */ + bool (*set_volume)(Mixer *mixer, unsigned volume, + Error &error); + + /** + * If true, then the mixer is automatically opened, even if + * its audio output is not open. If false, then the mixer is + * disabled as long as its audio output is closed. + */ + bool global; +}; + +#endif diff --git a/src/MixerType.cxx b/src/MixerType.cxx new file mode 100644 index 000000000..435079790 --- /dev/null +++ b/src/MixerType.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MixerType.hxx" + +#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/MixerType.hxx b/src/MixerType.hxx new file mode 100644 index 000000000..320a36c04 --- /dev/null +++ b/src/MixerType.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_MIXER_TYPE_HXX + +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/MusicBuffer.cxx b/src/MusicBuffer.cxx new file mode 100644 index 000000000..c811d8627 --- /dev/null +++ b/src/MusicBuffer.cxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "system/FatalError.hxx" + +#include <assert.h> + +MusicBuffer::MusicBuffer(unsigned num_chunks) + :buffer(num_chunks) { + if (buffer.IsOOM()) + FatalError("Failed to allocate buffer"); +} + +music_chunk * +MusicBuffer::Allocate() +{ + const ScopeLock protect(mutex); + return buffer.Allocate(); +} + +void +MusicBuffer::Return(music_chunk *chunk) +{ + assert(chunk != nullptr); + + const ScopeLock protect(mutex); + + if (chunk->other != nullptr) { + assert(chunk->other->other == nullptr); + buffer.Free(chunk->other); + } + + buffer.Free(chunk); +} diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx new file mode 100644 index 000000000..d2b23d43a --- /dev/null +++ b/src/MusicBuffer.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MUSIC_BUFFER_HXX +#define MPD_MUSIC_BUFFER_HXX + +#include "util/SliceBuffer.hxx" +#include "thread/Mutex.hxx" + +struct music_chunk; + +/** + * An allocator for #music_chunk objects. + */ +class MusicBuffer { + /** a mutex which protects #buffer */ + Mutex mutex; + + SliceBuffer<music_chunk> buffer; + +public: + /** + * Creates a new #MusicBuffer object. + * + * @param num_chunks the number of #music_chunk reserved in + * this buffer + */ + MusicBuffer(unsigned num_chunks); + +#ifndef NDEBUG + /** + * Check whether the buffer is empty. This call is not + * protected with the mutex, and may only be used while this + * object is inaccessible to other threads. + */ + bool IsEmptyUnsafe() const { + return buffer.IsEmpty(); + } +#endif + + /** + * Returns the total number of reserved chunks in this buffer. This + * is the same value which was passed to the constructor + * music_buffer_new(). + */ + gcc_pure + unsigned GetSize() const { + return buffer.GetCapacity(); + } + + /** + * Allocates a chunk from the buffer. When it is not used anymore, + * call Return(). + * + * @return an empty chunk or nullptr if there are no chunks + * available + */ + music_chunk *Allocate(); + + /** + * Returns a chunk to the buffer. It can be reused by + * Allocate() then. + */ + void Return(music_chunk *chunk); +}; + +#endif diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx new file mode 100644 index 000000000..9ffe4f509 --- /dev/null +++ b/src/MusicChunk.cxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MusicChunk.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> + +music_chunk::~music_chunk() +{ + delete tag; +} + +#ifndef NDEBUG +bool +music_chunk::CheckFormat(const AudioFormat other_format) const +{ + assert(other_format.IsValid()); + + return length == 0 || audio_format == other_format; +} +#endif + +void * +music_chunk::Write(const AudioFormat af, + float data_time, uint16_t _bit_rate, + size_t *max_length_r) +{ + assert(CheckFormat(af)); + assert(length == 0 || audio_format.IsValid()); + + if (length == 0) { + /* if the chunk is empty, nobody has set bitRate and + times yet */ + + bit_rate = _bit_rate; + times = data_time; + } + + const size_t frame_size = af.GetFrameSize(); + size_t num_frames = (sizeof(data) - length) / frame_size; + if (num_frames == 0) + return NULL; + +#ifndef NDEBUG + audio_format = af; +#endif + + *max_length_r = num_frames * frame_size; + return data + length; +} + +bool +music_chunk::Expand(const AudioFormat af, size_t _length) +{ + const size_t frame_size = af.GetFrameSize(); + + assert(length + _length <= sizeof(data)); + assert(audio_format == af); + + length += _length; + + return length + frame_size > sizeof(data); +} diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx new file mode 100644 index 000000000..3365b0bea --- /dev/null +++ b/src/MusicChunk.hxx @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MUSIC_CHUNK_HXX +#define MPD_MUSIC_CHUNK_HXX + +#include "ReplayGainInfo.hxx" + +#ifndef NDEBUG +#include "AudioFormat.hxx" +#endif + +#include <stdint.h> +#include <stddef.h> + +static constexpr size_t CHUNK_SIZE = 4096; + +struct AudioFormat; +struct Tag; + +/** + * A chunk of music data. Its format is defined by the + * MusicPipe::Push() caller. + */ +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; + + /** current bit rate of the source file */ + uint16_t bit_rate; + + /** the time stamp within the song */ + float times; + + /** + * An optional tag associated with this chunk (and the + * following chunks); appears at song boundaries. The tag + * object is owned by this chunk, and must be freed when this + * chunk is deinitialized. + */ + 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]; + +#ifndef NDEBUG + AudioFormat audio_format; +#endif + + music_chunk() + :other(nullptr), + length(0), + tag(nullptr), + replay_gain_serial(0) {} + + ~music_chunk(); + + bool IsEmpty() const { + return length == 0 && tag == nullptr; + } + +#ifndef NDEBUG + /** + * Checks if the audio format if the chunk is equal to the + * specified audio_format. + */ + gcc_pure + bool CheckFormat(AudioFormat audio_format) const; +#endif + + /** + * Prepares appending to the music chunk. Returns a buffer + * where you may write into. After you are finished, call + * music_chunk_expand(). + * + * @param chunk the music_chunk object + * @param audio_format the audio format for the appended data; + * must stay the same for the life cycle of this chunk + * @param data_time the time within the song + * @param bit_rate the current bit rate of the source file + * @param max_length_r the maximum write length is returned + * here + * @return a writable buffer, or NULL if the chunk is full + */ + void *Write(AudioFormat af, + float data_time, uint16_t bit_rate, + size_t *max_length_r); + + /** + * Increases the length of the chunk after the caller has written to + * the buffer returned by music_chunk_write(). + * + * @param chunk the music_chunk object + * @param audio_format the audio format for the appended data; must + * stay the same for the life cycle of this chunk + * @param length the number of bytes which were appended + * @return true if the chunk is full + */ + bool Expand(AudioFormat af, size_t length); +}; + +#endif diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx new file mode 100644 index 000000000..eadf526e1 --- /dev/null +++ b/src/MusicPipe.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" + +#ifndef NDEBUG + +bool +MusicPipe::Contains(const music_chunk *chunk) const +{ + const ScopeLock protect(mutex); + + for (const struct music_chunk *i = head; i != nullptr; i = i->next) + if (i == chunk) + return true; + + return false; +} + +#endif + +music_chunk * +MusicPipe::Shift() +{ + const ScopeLock protect(mutex); + + music_chunk *chunk = head; + if (chunk != nullptr) { + assert(!chunk->IsEmpty()); + + head = chunk->next; + --size; + + if (head == nullptr) { + assert(size == 0); + assert(tail_r == &chunk->next); + + tail_r = &head; + } else { + assert(size > 0); + assert(tail_r != &chunk->next); + } + +#ifndef NDEBUG + /* poison the "next" reference */ + chunk->next = (music_chunk *)(void *)0x01010101; + + if (size == 0) + audio_format.Clear(); +#endif + } + + return chunk; +} + +void +MusicPipe::Clear(MusicBuffer &buffer) +{ + music_chunk *chunk; + + while ((chunk = Shift()) != nullptr) + buffer.Return(chunk); +} + +void +MusicPipe::Push(music_chunk *chunk) +{ + assert(!chunk->IsEmpty()); + assert(chunk->length == 0 || chunk->audio_format.IsValid()); + + const ScopeLock protect(mutex); + + assert(size > 0 || !audio_format.IsDefined()); + assert(!audio_format.IsDefined() || + chunk->CheckFormat(audio_format)); + +#ifndef NDEBUG + if (!audio_format.IsDefined() && chunk->length > 0) + audio_format = chunk->audio_format; +#endif + + chunk->next = nullptr; + *tail_r = chunk; + tail_r = &chunk->next; + + ++size; +} diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx new file mode 100644 index 000000000..2133bc086 --- /dev/null +++ b/src/MusicPipe.hxx @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PIPE_H +#define MPD_PIPE_H + +#include "thread/Mutex.hxx" +#include "gcc.h" + +#ifndef NDEBUG +#include "AudioFormat.hxx" +#endif + +#include <assert.h> + +struct music_chunk; +class MusicBuffer; + +/** + * A queue of #music_chunk objects. One party appends chunks at the + * tail, and the other consumes them from the head. + */ +class MusicPipe { + /** the first chunk */ + music_chunk *head; + + /** a pointer to the tail of the chunk */ + music_chunk **tail_r; + + /** the current number of chunks */ + unsigned size; + + /** a mutex which protects #head and #tail_r */ + mutable Mutex mutex; + +#ifndef NDEBUG + AudioFormat audio_format; +#endif + +public: + /** + * Creates a new #MusicPipe object. It is empty. + */ + MusicPipe() + :head(nullptr), tail_r(&head), size(0) { +#ifndef NDEBUG + audio_format.Clear(); +#endif + } + + /** + * Frees the object. It must be empty now. + */ + ~MusicPipe() { + assert(head == nullptr); + assert(tail_r == &head); + } + +#ifndef NDEBUG + /** + * Checks if the audio format if the chunk is equal to the specified + * audio_format. + */ + gcc_pure + bool CheckFormat(AudioFormat other) const { + return !audio_format.IsDefined() || + audio_format == other; + } + + /** + * Checks if the specified chunk is enqueued in the music pipe. + */ + gcc_pure + bool Contains(const music_chunk *chunk) const; +#endif + + /** + * Returns the first #music_chunk from the pipe. Returns + * nullptr if the pipe is empty. + */ + gcc_pure + const music_chunk *Peek() const { + return head; + } + + /** + * Removes the first chunk from the head, and returns it. + */ + music_chunk *Shift(); + + /** + * Clears the whole pipe and returns the chunks to the buffer. + * + * @param buffer the buffer object to return the chunks to + */ + void Clear(MusicBuffer &buffer); + + /** + * Pushes a chunk to the tail of the pipe. + */ + void Push(music_chunk *chunk); + + /** + * Returns the number of chunks currently in this pipe. + */ + gcc_pure + unsigned GetSize() const { + return size; + } + + gcc_pure + bool IsEmpty() const { + return GetSize() == 0; + } +}; + +#endif diff --git a/src/OtherCommands.cxx b/src/OtherCommands.cxx new file mode 100644 index 000000000..52899d184 --- /dev/null +++ b/src/OtherCommands.cxx @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OtherCommands.hxx" +#include "DatabaseCommands.hxx" +#include "CommandError.hxx" +#include "UpdateGlue.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "SongPrint.hxx" +#include "TagPrint.hxx" +#include "TimePrint.hxx" +#include "Mapper.hxx" +#include "DecoderPrint.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Volume.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/Path.hxx" +#include "Stats.hxx" +#include "Permission.hxx" +#include "PlaylistFile.hxx" +#include "ClientFile.hxx" +#include "ClientInternal.hxx" +#include "Idle.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static void +print_spl_list(Client *client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_urlhandlers(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + if (client_is_local(client)) + client_puts(client, "handler: file://\n"); + print_supported_uri_schemes(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_decoders(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + decoder_list_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_tagtypes(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + tag_print_types(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_kill(gcc_unused Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + return COMMAND_RETURN_KILL; +} + +enum command_return +handle_close(gcc_unused Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + return COMMAND_RETURN_CLOSE; +} + +enum command_return +handle_lsinfo(Client *client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + if (strncmp(uri, "file:///", 8) == 0) { + /* print information about an arbitrary local file */ + const char *path_utf8 = uri + 7; + const Path path_fs = Path::FromUTF8(path_utf8); + + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return COMMAND_RETURN_ERROR; + } + + Error error; + if (!client_allow_file(client, path_fs, error)) + return print_error(client, error); + + Song *song = Song::LoadFile(path_utf8, nullptr); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return COMMAND_RETURN_ERROR; + } + + song_print_info(client, song); + song->Free(); + return COMMAND_RETURN_OK; + } + + enum command_return result = handle_lsinfo2(client, argc, argv); + if (result != COMMAND_RETURN_OK) + return result; + + if (isRootDirectory(uri)) { + Error error; + const auto &list = ListPlaylistFiles(error); + print_spl_list(client, list); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_update(Client *client, gcc_unused int argc, char *argv[]) +{ + const char *path = NULL; + unsigned ret; + + assert(argc <= 2); + 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 = 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; + } +} + +enum command_return +handle_rescan(Client *client, gcc_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; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_setvol(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned level; + bool success; + + if (!check_unsigned(client, &level, argv[1])) + return COMMAND_RETURN_ERROR; + + if (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"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_stats(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + stats_print(client); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_ping(gcc_unused Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + return COMMAND_RETURN_OK; +} + +enum command_return +handle_password(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned permission = 0; + + if (getPermissionFromPassword(argv[1], &permission) < 0) { + command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); + return COMMAND_RETURN_ERROR; + } + + client_set_permission(client, permission); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_config(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + if (!client_is_local(client)) { + command_error(client, ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); + return COMMAND_RETURN_ERROR; + } + + const char *path = mapper_get_music_directory_utf8(); + if (path != NULL) + client_printf(client, "music_directory: %s\n", path); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_idle(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + unsigned flags = 0, j; + int i; + const char *const* idle_names; + + idle_names = idle_get_names(); + for (i = 1; i < argc; ++i) { + if (!argv[i]) + continue; + + for (j = 0; idle_names[j]; ++j) { + if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { + flags |= (1 << j); + } + } + } + + /* No argument means that the client wants to receive everything */ + if (flags == 0) + flags = ~0; + + /* enable "idle" mode on this client */ + client->IdleWait(flags); + + return COMMAND_RETURN_IDLE; +} diff --git a/src/OtherCommands.hxx b/src/OtherCommands.hxx new file mode 100644 index 000000000..564ad38e7 --- /dev/null +++ b/src/OtherCommands.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OTHER_COMMANDS_HXX +#define MPD_OTHER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_urlhandlers(Client *client, int argc, char *argv[]); + +enum command_return +handle_decoders(Client *client, int argc, char *argv[]); + +enum command_return +handle_tagtypes(Client *client, int argc, char *argv[]); + +enum command_return +handle_kill(Client *client, int argc, char *argv[]); + +enum command_return +handle_close(Client *client, int argc, char *argv[]); + +enum command_return +handle_lsinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_update(Client *client, int argc, char *argv[]); + +enum command_return +handle_rescan(Client *client, int argc, char *argv[]); + +enum command_return +handle_setvol(Client *client, int argc, char *argv[]); + +enum command_return +handle_stats(Client *client, int argc, char *argv[]); + +enum command_return +handle_ping(Client *client, int argc, char *argv[]); + +enum command_return +handle_password(Client *client, int argc, char *argv[]); + +enum command_return +handle_config(Client *client, int argc, char *argv[]); + +enum command_return +handle_idle(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx new file mode 100644 index 000000000..73cbaf183 --- /dev/null +++ b/src/OutputAPI.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_API_HXX +#define MPD_OUTPUT_API_HxX + +#include "OutputPlugin.hxx" +#include "OutputInternal.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "ConfigData.hxx" + +#endif diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx new file mode 100644 index 000000000..18113f860 --- /dev/null +++ b/src/OutputAll.cxx @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputAll.hxx" +#include "PlayerControl.hxx" +#include "OutputInternal.hxx" +#include "OutputControl.hxx" +#include "OutputError.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "notify.hxx" + +#include <assert.h> +#include <string.h> + +static AudioFormat input_audio_format; + +static struct audio_output **audio_outputs; +static unsigned int num_audio_outputs; + +/** + * The #MusicBuffer object where consumed chunks are returned. + */ +static MusicBuffer *g_music_buffer; + +/** + * The #MusicPipe object which feeds all audio outputs. It is filled + * by audio_output_all_play(). + */ +static MusicPipe *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; +} + +struct audio_output * +audio_output_get(unsigned i) +{ + assert(i < num_audio_outputs); + + assert(audio_outputs[i] != NULL); + + return audio_outputs[i]; +} + +struct audio_output * +audio_output_find(const char *name) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_output_get(i); + + if (strcmp(ao->name, name) == 0) + return ao; + } + + /* name not found */ + return NULL; +} + +gcc_const +static unsigned +audio_output_config_count(void) +{ + unsigned int nr = 0; + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) + nr++; + if (!nr) + nr = 1; /* we'll always have at least one device */ + return nr; +} + +void +audio_output_all_init(struct player_control *pc) +{ + const struct config_param *param = NULL; + unsigned int i; + Error error; + + num_audio_outputs = audio_output_config_count(); + audio_outputs = g_new(struct audio_output *, num_audio_outputs); + + const config_param empty; + + for (i = 0; i < num_audio_outputs; i++) + { + unsigned int j; + + param = config_get_next_param(CONF_AUDIO_OUTPUT, param); + if (param == nullptr) { + /* only allow param to be nullptr if there + just one audio output */ + assert(i == 0); + assert(num_audio_outputs == 1); + + param = ∅ + } + + audio_output *output = audio_output_new(*param, pc, error); + if (output == NULL) { + if (param != NULL) + FormatFatalError("line %i: %s", + param->line, + error.GetMessage()); + else + FatalError(error); + } + + audio_outputs[i] = output; + + /* require output names to be unique: */ + for (j = 0; j < i; j++) { + if (!strcmp(output->name, audio_outputs[j]->name)) { + FormatFatalError("output devices with identical " + "names: %s", output->name); + } + } + } +} + +void +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]); + } + + g_free(audio_outputs); + audio_outputs = NULL; + num_audio_outputs = 0; +} + +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; + + ao->mutex.lock(); + enabled = ao->really_enabled; + ao->mutex.unlock(); + + 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 + * command. + */ +static bool +audio_output_all_finished(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + const ScopeLock protect(ao->mutex); + if (audio_output_is_open(ao) && + !audio_output_command_is_finished(ao)) + return false; + } + + return true; +} + +static void audio_output_wait_all(void) +{ + while (!audio_output_all_finished()) + audio_output_client_notify.Wait(); +} + +/** + * Signals all audio outputs which are open. + */ +static void +audio_output_allow_play_all(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_allow_play(audio_outputs[i]); +} + +static void +audio_output_reset_reopen(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + if (!ao->open && ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } +} + +/** + * Resets the "reopen" flag on all audio devices. MPD should + * immediately retry to open the device instead of waiting for the + * timeout when the user wants to start playback. + */ +static void +audio_output_all_reset_reopen(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + audio_output_reset_reopen(ao); + } +} + +/** + * Opens all output devices which are enabled, but closed. + * + * @return true if there is at least open output device which is open + */ +static bool +audio_output_all_update(void) +{ + unsigned int i; + bool ret = false; + + if (!input_audio_format.IsDefined()) + return false; + + for (i = 0; i < num_audio_outputs; ++i) + ret = audio_output_update(audio_outputs[i], + input_audio_format, *g_mp) || ret; + + return ret; +} + +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_set_replay_gain_mode(audio_outputs[i], mode); +} + +bool +audio_output_all_play(struct music_chunk *chunk, Error &error) +{ + bool ret; + unsigned int i; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + assert(chunk != NULL); + assert(chunk->CheckFormat(input_audio_format)); + + ret = audio_output_all_update(); + if (!ret) { + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + return false; + } + + g_mp->Push(chunk); + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_play(audio_outputs[i]); + + return true; +} + +bool +audio_output_all_open(const AudioFormat audio_format, + MusicBuffer &buffer, + Error &error) +{ + bool ret = false, enabled = false; + unsigned int i; + + assert(g_music_buffer == NULL || g_music_buffer == &buffer); + assert((g_mp == NULL) == (g_music_buffer == NULL)); + + g_music_buffer = &buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(g_mp == NULL || g_mp->CheckFormat(audio_format)); + + if (g_mp == NULL) + g_mp = new MusicPipe(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(g_mp->IsEmpty() || audio_format == input_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) { + if (audio_outputs[i]->enabled) + enabled = true; + + if (audio_outputs[i]->open) + ret = true; + } + + if (!enabled) + error.Set(output_domain, "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + + if (!ret) + /* close all devices if there was an error */ + audio_output_all_close(); + + return ret; +} + +/** + * Has the specified audio output already consumed this chunk? + */ +static bool +chunk_is_consumed_in(const struct audio_output *ao, + const struct music_chunk *chunk) +{ + if (!ao->open) + return true; + + if (ao->chunk == NULL) + return false; + + assert(chunk == ao->chunk || g_mp->Contains(ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != NULL); + return true; + } + + return ao->chunk_finished && chunk->next == NULL; +} + +/** + * Has this chunk been consumed by all audio outputs? + */ +static bool +chunk_is_consumed(const struct music_chunk *chunk) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + const ScopeLock protect(ao->mutex); + if (!chunk_is_consumed_in(ao, chunk)) + return false; + } + + return true; +} + +/** + * There's only one chunk left in the pipe (#g_mp), and all audio + * outputs have consumed it already. Clear the reference. + */ +static void +clear_tail_chunk(gcc_unused const struct music_chunk *chunk, bool *locked) +{ + assert(chunk->next == NULL); + assert(g_mp->Contains(chunk)); + + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + /* this mutex will be unlocked by the caller when it's + ready */ + ao->mutex.lock(); + locked[i] = ao->open; + + if (!locked[i]) { + ao->mutex.unlock(); + continue; + } + + assert(ao->chunk == chunk); + assert(ao->chunk_finished); + ao->chunk = NULL; + } +} + +unsigned +audio_output_all_check(void) +{ + const struct music_chunk *chunk; + bool is_tail; + struct music_chunk *shifted; + bool locked[num_audio_outputs]; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + + while ((chunk = g_mp->Peek()) != nullptr) { + assert(!g_mp->IsEmpty()); + + if (!chunk_is_consumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return g_mp->GetSize(); + + 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 + chunk reference in all outputs */ + clear_tail_chunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = g_mp->Shift(); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0; i < num_audio_outputs; ++i) + if (locked[i]) + audio_outputs[i]->mutex.unlock(); + + /* return the chunk to the buffer */ + g_music_buffer->Return(shifted); + } + + return 0; +} + +bool +audio_output_all_wait(struct player_control *pc, unsigned threshold) +{ + pc->Lock(); + + if (audio_output_all_check() < threshold) { + pc->Unlock(); + return true; + } + + pc->Wait(); + pc->Unlock(); + + return audio_output_all_check() < threshold; +} + +void +audio_output_all_pause(void) +{ + unsigned int i; + + audio_output_all_update(); + + for (i = 0; i < num_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(); +} + +void +audio_output_all_cancel(void) +{ + unsigned int i; + + /* send the cancel() command to all audio outputs */ + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_cancel(audio_outputs[i]); + + audio_output_wait_all(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (g_mp != NULL) + g_mp->Clear(*g_music_buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + audio_output_allow_play_all(); + + /* invalidate elapsed_time */ + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_close(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_close(audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + g_mp->Clear(*g_music_buffer); + delete g_mp; + g_mp = NULL; + } + + g_music_buffer = NULL; + + input_audio_format.Clear(); + + 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); + + g_mp->Clear(*g_music_buffer); + delete g_mp; + g_mp = NULL; + } + + g_music_buffer = NULL; + + input_audio_format.Clear(); + + 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/OutputAll.hxx b/src/OutputAll.hxx new file mode 100644 index 000000000..7053c0580 --- /dev/null +++ b/src/OutputAll.hxx @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for dealing with all configured (enabled) audion outputs + * at once. + * + */ + +#ifndef OUTPUT_ALL_H +#define OUTPUT_ALL_H + +#include "ReplayGainInfo.hxx" +#include "gcc.h" + +struct AudioFormat; +class MusicBuffer; +struct music_chunk; +struct player_control; +class Error; + +/** + * Global initialization: load audio outputs from the configuration + * file and initialize them. + */ +void +audio_output_all_init(struct player_control *pc); + +/** + * Global finalization: free memory occupied by audio outputs. All + */ +void +audio_output_all_finish(void); + +/** + * Returns the total number of audio output devices, including those + * who are disabled right now. + */ +gcc_const +unsigned int audio_output_count(void); + +/** + * Returns the "i"th audio output device. + */ +gcc_const +struct audio_output * +audio_output_get(unsigned i); + +/** + * Returns the audio output device with the specified name. Returns + * NULL if the name does not exist. + */ +gcc_pure +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 + * @param buffer the #music_buffer where consumed #music_chunk objects + * should be returned + * @return true on success, false on failure + */ +bool +audio_output_all_open(AudioFormat audio_format, + MusicBuffer &buffer, + Error &error); + +/** + * Closes all audio outputs. + */ +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); + +void +audio_output_all_set_replay_gain_mode(enum replay_gain_mode mode); + +/** + * Enqueue a #music_chunk object for playing, i.e. pushes it to a + * #MusicPipe. + * + * @param chunk the #music_chunk object to be played + * @return true on success, false if no audio output was able to play + * (all closed then) + */ +bool +audio_output_all_play(struct music_chunk *chunk, Error &error); + +/** + * Checks if the output devices have drained their music pipe, and + * returns the consumed music chunks to the #music_buffer. + * + * @return the number of chunks to play left in the #MusicPipe + */ +unsigned +audio_output_all_check(void); + +/** + * Checks if the size of the #MusicPipe is below the #threshold. If + * not, it attempts to synchronize with all output threads, and waits + * until another #music_chunk is finished. + * + * @param threshold the maximum number of chunks in the pipe + * @return true if there are less than #threshold chunks in the pipe + */ +bool +audio_output_all_wait(struct player_control *pc, unsigned threshold); + +/** + * Puts all audio outputs into pause mode. Most implementations will + * simply close it then. + */ +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. + */ +gcc_pure +float +audio_output_all_get_elapsed_time(void); + +#endif diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx new file mode 100644 index 000000000..f6b35c6ed --- /dev/null +++ b/src/OutputCommand.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#include "config.h" +#include "OutputCommand.hxx" +#include "OutputAll.hxx" +#include "OutputInternal.hxx" +#include "OutputPlugin.hxx" +#include "PlayerControl.hxx" +#include "MixerControl.hxx" +#include "Idle.hxx" + +extern unsigned audio_output_state_version; + +bool +audio_output_enable_index(unsigned idx) +{ + struct audio_output *ao; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + if (ao->enabled) + return true; + + ao->enabled = true; + idle_add(IDLE_OUTPUT); + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_disable_index(unsigned idx) +{ + struct audio_output *ao; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + if (!ao->enabled) + return true; + + ao->enabled = false; + idle_add(IDLE_OUTPUT); + + Mixer *mixer = ao->mixer; + if (mixer != NULL) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_toggle_index(unsigned idx) +{ + struct audio_output *ao; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + const bool enabled = ao->enabled = !ao->enabled; + idle_add(IDLE_OUTPUT); + + if (!enabled) { + Mixer *mixer = ao->mixer; + if (mixer != nullptr) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + } + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} diff --git a/src/OutputCommand.hxx b/src/OutputCommand.hxx new file mode 100644 index 000000000..46fab92c5 --- /dev/null +++ b/src/OutputCommand.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX + +/** + * Enables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_enable_index(unsigned idx); + +/** + * Disables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_disable_index(unsigned idx); + +/** + * Toggles an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_toggle_index(unsigned idx); + +#endif diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx new file mode 100644 index 000000000..3281104d1 --- /dev/null +++ b/src/OutputCommands.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputCommands.hxx" +#include "OutputPrint.hxx" +#include "OutputCommand.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include <string.h> + +enum command_return +handle_enableoutput(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_enable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_disableoutput(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = audio_output_disable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_toggleoutput(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned device; + if (!check_unsigned(client, &device, argv[1])) + return COMMAND_RETURN_ERROR; + + if (!audio_output_toggle_index(device)) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_devices(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + printAudioDevices(client); + + return COMMAND_RETURN_OK; +} diff --git a/src/OutputCommands.hxx b/src/OutputCommands.hxx new file mode 100644 index 000000000..5642a0680 --- /dev/null +++ b/src/OutputCommands.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_COMMANDS_HXX +#define MPD_OUTPUT_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_enableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_disableoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_toggleoutput(Client *client, int argc, char *argv[]); + +enum command_return +handle_devices(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx new file mode 100644 index 000000000..3dc9c470b --- /dev/null +++ b/src/OutputControl.cxx @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputControl.hxx" +#include "OutputThread.hxx" +#include "OutputInternal.hxx" +#include "OutputPlugin.hxx" +#include "OutputError.hxx" +#include "MixerPlugin.hxx" +#include "MixerControl.hxx" +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +enum { + /** after a failure, wait this number of seconds before + automatically reopening the device */ + REOPEN_AFTER = 10, +}; + +struct notify audio_output_client_notify; + +/** + * Waits for command completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_wait(struct audio_output *ao) +{ + while (ao->command != AO_COMMAND_NONE) { + ao->mutex.unlock(); + audio_output_client_notify.Wait(); + ao->mutex.lock(); + } +} + +/** + * Sends a command to the #audio_output object, but does not wait for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) +{ + assert(ao->command == AO_COMMAND_NONE); + ao->command = cmd; + ao->cond.signal(); +} + +/** + * Sends a command to the #audio_output object and waits for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void +ao_command(struct audio_output *ao, enum audio_output_command cmd) +{ + ao_command_async(ao, cmd); + ao_command_wait(ao); +} + +/** + * Lock the #audio_output object and execute the command + * synchronously. + */ +static void +ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) +{ + const ScopeLock protect(ao->mutex); + ao_command(ao, cmd); +} + +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode) +{ + if (ao->replay_gain_filter != NULL) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); +} + +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); + } + + ao_lock_command(ao, AO_COMMAND_ENABLE); +} + +void +audio_output_disable(struct audio_output *ao) +{ + 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; + } + + ao_lock_command(ao, AO_COMMAND_DISABLE); +} + +/** + * Object must be locked (and unlocked) by the caller. + */ +static bool +audio_output_open(struct audio_output *ao, + const AudioFormat audio_format, + const MusicPipe &mp) +{ + bool open; + + assert(ao != NULL); + assert(ao->allow_play); + assert(audio_format.IsValid()); + + if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + if (ao->open && audio_format == ao->in_audio_format) { + assert(ao->pipe == &mp || + (ao->always_on && ao->pause)); + + if (ao->pause) { + ao->chunk = NULL; + ao->pipe = ∓ + + /* 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 + to flush the device buffer anyway */ + + /* we're not using audio_output_cancel() here, + because that function is asynchronous */ + ao_command(ao, AO_COMMAND_CANCEL); + } + + return true; + } + + ao->in_audio_format = audio_format; + ao->chunk = NULL; + + ao->pipe = ∓ + + 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->mixer != NULL) { + Error error; + if (!mixer_open(ao->mixer, error)) + FormatWarning(output_domain, + "Failed to open mixer for '%s'", + ao->name); + } + + 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); + assert(ao->allow_play); + + 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 AudioFormat audio_format, + const MusicPipe &mp) +{ + const ScopeLock protect(ao->mutex); + + if (ao->enabled && ao->really_enabled) { + if (ao->fail_timer == NULL || + g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { + return audio_output_open(ao, audio_format, mp); + } + } else if (audio_output_is_open(ao)) + audio_output_close_locked(ao); + + return false; +} + +void +audio_output_play(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + assert(ao->allow_play); + + if (audio_output_is_open(ao)) + ao->cond.signal(); +} + +void audio_output_pause(struct audio_output *ao) +{ + if (ao->mixer != NULL && ao->plugin->pause == NULL) + /* the device has no pause mode: close the mixer, + unless its "global" flag is set (checked by + mixer_auto_close()) */ + mixer_auto_close(ao->mixer); + + const ScopeLock protect(ao->mutex); + + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_PAUSE); +} + +void +audio_output_drain_async(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_DRAIN); +} + +void audio_output_cancel(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + if (audio_output_is_open(ao)) { + ao->allow_play = false; + ao_command_async(ao, AO_COMMAND_CANCEL); + } +} + +void +audio_output_allow_play(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + ao->allow_play = true; + if (audio_output_is_open(ao)) + ao->cond.signal(); +} + +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) +{ + assert(ao != NULL); + assert(!ao->open || ao->fail_timer == NULL); + + const ScopeLock protect(ao->mutex); + audio_output_close_locked(ao); +} + +void audio_output_finish(struct audio_output *ao) +{ + audio_output_close(ao); + + assert(ao->fail_timer == NULL); + + if (ao->thread != NULL) { + assert(ao->allow_play); + ao_lock_command(ao, AO_COMMAND_KILL); + g_thread_join(ao->thread); + ao->thread = NULL; + } + + audio_output_free(ao); +} diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx new file mode 100644 index 000000000..25fbec1e7 --- /dev/null +++ b/src/OutputControl.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +#include "ReplayGainInfo.hxx" + +#include <stddef.h> + +struct audio_output; +struct AudioFormat; +struct config_param; +class MusicPipe; +struct player_control; + +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + enum replay_gain_mode mode); + +/** + * 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. + * + * @return true if the device is open + */ +bool +audio_output_update(struct audio_output *ao, + AudioFormat audio_format, + const MusicPipe &mp); + +void +audio_output_play(struct audio_output *ao); + +void audio_output_pause(struct audio_output *ao); + +void +audio_output_drain_async(struct audio_output *ao); + +/** + * Clear the "allow_play" flag and send the "CANCEL" command + * asynchronously. To finish the operation, the caller has to call + * audio_output_allow_play(). + */ +void audio_output_cancel(struct audio_output *ao); + +/** + * Set the "allow_play" and signal the thread. + */ +void +audio_output_allow_play(struct audio_output *ao); + +void audio_output_close(struct audio_output *ao); + +/** + * 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/OutputError.cxx b/src/OutputError.cxx new file mode 100644 index 000000000..a18bfff23 --- /dev/null +++ b/src/OutputError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputError.hxx" +#include "util/Domain.hxx" + +const Domain output_domain("output"); diff --git a/src/OutputError.hxx b/src/OutputError.hxx new file mode 100644 index 000000000..22a11fdbd --- /dev/null +++ b/src/OutputError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_ERROR_HXX +#define MPD_OUTPUT_ERROR_HXX + +extern const class Domain output_domain; + +#endif diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx new file mode 100644 index 000000000..2346161aa --- /dev/null +++ b/src/OutputFinish.cxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputInternal.hxx" +#include "OutputPlugin.hxx" +#include "MixerControl.hxx" +#include "FilterInternal.hxx" + +#include <assert.h> + +void +ao_base_finish(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + if (ao->mixer != NULL) + mixer_free(ao->mixer); + + delete ao->replay_gain_filter; + delete ao->other_replay_gain_filter; + delete ao->filter; +} + +void +audio_output_free(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + ao_plugin_finish(ao); +} diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx new file mode 100644 index 000000000..8a31a9b97 --- /dev/null +++ b/src/OutputInit.cxx @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputInternal.hxx" +#include "OutputControl.hxx" +#include "OutputList.hxx" +#include "OutputError.hxx" +#include "OutputAPI.hxx" +#include "FilterConfig.hxx" +#include "AudioParser.hxx" +#include "MixerList.hxx" +#include "MixerType.hxx" +#include "MixerControl.hxx" +#include "mixer/SoftwareMixerPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "filter/AutoConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "filter/ChainFilterPlugin.hxx" +#include "ConfigError.hxx" +#include "ConfigGlobal.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +#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(Error &error) +{ + LogInfo(output_domain, "Attempt to detect audio output device"); + + audio_output_plugins_for_each(plugin) { + if (plugin->test_default_device == NULL) + continue; + + FormatInfo(output_domain, + "Attempting to detect a %s audio device", + plugin->name); + if (ao_plugin_test_default_device(plugin)) + return plugin; + } + + error.Set(output_domain, "Unable to detect an audio device"); + 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. + */ +gcc_pure +static enum mixer_type +audio_output_mixer_type(const config_param ¶m) +{ + /* read the local "mixer_type" setting */ + const char *p = param.GetBlockValue("mixer_type"); + if (p != NULL) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!param.GetBlockValue("mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string(CONF_MIXER_TYPE, + "hardware")); +} + +static Mixer * +audio_output_load_mixer(struct audio_output *ao, + const config_param ¶m, + const struct mixer_plugin *plugin, + Filter &filter_chain, + Error &error) +{ + 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); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, nullptr, + config_param(), + IgnoreError()); + assert(mixer != NULL); + + filter_chain_append(filter_chain, "software_mixer", + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + gcc_unreachable(); +} + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const config_param ¶m, Error &error) +{ + assert(ao != NULL); + assert(plugin != NULL); + assert(plugin->finish != NULL); + assert(plugin->open != NULL); + assert(plugin->close != NULL); + assert(plugin->play != NULL); + + if (!param.IsNull()) { + ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME); + if (ao->name == NULL) { + error.Set(config_domain, + "Missing \"name\" configuration"); + return false; + } + + const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); + if (p != NULL) { + bool success = + audio_format_parse(ao->config_audio_format, + p, true, error); + if (!success) + return false; + } else + ao->config_audio_format.Clear(); + } else { + ao->name = "default detected output"; + + ao->config_audio_format.Clear(); + } + + ao->plugin = plugin; + ao->tags = param.GetBlockValue("tags", true); + ao->always_on = param.GetBlockValue("always_on", false); + ao->enabled = param.GetBlockValue("enabled", true); + ao->really_enabled = false; + ao->open = false; + ao->pause = false; + ao->allow_play = true; + ao->fail_timer = NULL; + + /* set up the filter chain */ + + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); + + /* create the normalization filter (if configured) */ + + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + Filter *normalize_filter = + filter_new(&normalize_filter_plugin, config_param(), + IgnoreError()); + assert(normalize_filter != NULL); + + filter_chain_append(*ao->filter, "normalize", + autoconvert_filter_new(normalize_filter)); + } + + Error filter_error; + filter_chain_parse(*ao->filter, + param.GetBlockValue(AUDIO_FILTERS, ""), + filter_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 (filter_error.IsDefined()) + FormatError(filter_error, + "Failed to initialize filter chain for '%s'", + ao->name); + + ao->thread = NULL; + ao->command = AO_COMMAND_NONE; + + ao->mixer = NULL; + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + + /* done */ + + return true; +} + +static bool +audio_output_setup(struct audio_output *ao, const config_param ¶m, + Error &error) +{ + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + param.GetBlockValue("replay_gain_handler", "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, IgnoreError()); + assert(ao->replay_gain_filter != NULL); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, + IgnoreError()); + assert(ao->other_replay_gain_filter != NULL); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + } + + /* set up the mixer */ + + Error mixer_error; + ao->mixer = audio_output_load_mixer(ao, param, + ao->plugin->mixer_plugin, + *ao->filter, mixer_error); + if (ao->mixer == NULL && mixer_error.IsDefined()) + FormatError(mixer_error, + "Failed to initialize hardware mixer for '%s'", + ao->name); + + /* 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 + FormatError(output_domain, + "No such mixer for output '%s'", ao->name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao->replay_gain_filter != NULL) { + error.Set(config_domain, + "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, config_param(), + IgnoreError()); + assert(ao->convert_filter != NULL); + + filter_chain_append(*ao->filter, "convert", ao->convert_filter); + + return true; +} + +struct audio_output * +audio_output_new(const config_param ¶m, + struct player_control *pc, + Error &error) +{ + const struct audio_output_plugin *plugin; + + if (!param.IsNull()) { + const char *p; + + p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); + if (p == NULL) { + error.Set(config_domain, + "Missing \"type\" configuration"); + return nullptr; + } + + plugin = audio_output_plugin_get(p); + if (plugin == NULL) { + error.Format(config_domain, + "No such audio output plugin: %s", p); + return nullptr; + } + } else { + LogWarning(output_domain, + "No 'audio_output' defined in config file"); + + plugin = audio_output_detect(error); + if (plugin == NULL) + return nullptr; + + FormatInfo(output_domain, + "Successfully detected a %s audio device", + plugin->name); + } + + struct audio_output *ao = ao_plugin_init(plugin, param, error); + if (ao == NULL) + return NULL; + + if (!audio_output_setup(ao, param, error)) { + ao_plugin_finish(ao); + return NULL; + } + + ao->player_control = pc; + return ao; +} diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx new file mode 100644 index 000000000..7e615290e --- /dev/null +++ b/src/OutputInternal.hxx @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_INTERNAL_HXX +#define MPD_OUTPUT_INTERNAL_HXX + +#include "AudioFormat.hxx" +#include "pcm/PcmBuffer.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <time.h> + +class Error; +class Filter; +class MusicPipe; +struct config_param; +typedef struct _GThread GThread; +typedef struct _GTimer GTimer; + +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 +}; + +struct audio_output { + /** + * The device's configured display name. + */ + const char *name; + + /** + * The plugin which implements this output device. + */ + const struct audio_output_plugin *plugin; + + /** + * The #mixer object associated with this audio output device. + * May be NULL if none is available, or if software volume is + * configured. + */ + class Mixer *mixer; + + /** + * Will this output receive tags from the decoder? The + * default is true, but it may be configured to false to + * suppress sending tags to the output. + */ + bool tags; + + /** + * Shall this output always play something (i.e. silence), + * even when playback is stopped? + */ + bool always_on; + + /** + * Has the user enabled this device? + */ + 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. + * It is protected with #mutex: write accesses inside the + * output thread and read accesses outside of it may only be + * performed while the lock is held. + */ + bool open; + + /** + * Is the device paused? i.e. the output thread is in the + * ao_pause() loop. + */ + bool pause; + + /** + * When this flag is set, the output thread will not do any + * playback. It will wait until the flag is cleared. + * + * This is used to synchronize the "clear" operation on the + * shared music pipe during the CANCEL command. + */ + bool allow_play; + + /** + * If not NULL, the device has failed, and this timer is used + * to estimate how long it should stay disabled (unless + * explicitly reopened with "play"). + */ + GTimer *fail_timer; + + /** + * The configured audio format. + */ + AudioFormat config_audio_format; + + /** + * The audio_format in which audio data is received from the + * player thread (which in turn receives it from the decoder). + */ + AudioFormat in_audio_format; + + /** + * The audio_format which is really sent to the device. This + * is basically config_audio_format (if configured) or + * in_audio_format, but may have been modified by + * plugin->open(). + */ + AudioFormat out_audio_format; + + /** + * The buffer used to allocate the cross-fading result. + */ + PcmBuffer cross_fade_buffer; + + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + Filter *filter; + + /** + * The replay_gain_filter_plugin instance of this audio + * output. + */ + 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. + */ + 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. + */ + 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. + */ + enum audio_output_command command; + + /** + * The music pipe which provides music chunks to be played. + */ + const MusicPipe *pipe; + + /** + * This mutex protects #open, #fail_timer, #chunk and + * #chunk_finished. + */ + Mutex mutex; + + /** + * This condition object wakes up the output thread after + * #command has been set. + */ + Cond cond; + + /** + * The player_control object which "owns" this output. This + * object is needed to signal command completion. + */ + struct player_control *player_control; + + /** + * The #music_chunk which is currently being played. All + * chunks before this one may be returned to the + * #music_buffer, because they are not going to be used by + * this output anymore. + */ + const struct music_chunk *chunk; + + /** + * Has the output finished playing #chunk? + */ + bool chunk_finished; +}; + +/** + * Notify object used by the thread's client, i.e. we will send a + * notify signal to this object, expecting the caller to wait on it. + */ +extern struct notify audio_output_client_notify; + +static inline bool +audio_output_is_open(const struct audio_output *ao) +{ + return ao->open; +} + +static inline bool +audio_output_command_is_finished(const struct audio_output *ao) +{ + return ao->command == AO_COMMAND_NONE; +} + +struct audio_output * +audio_output_new(const config_param ¶m, + struct player_control *pc, + Error &error); + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const config_param ¶m, Error &error); + +void +ao_base_finish(struct audio_output *ao); + +void +audio_output_free(struct audio_output *ao); + +#endif diff --git a/src/OutputList.cxx b/src/OutputList.cxx new file mode 100644 index 000000000..670131ed9 --- /dev/null +++ b/src/OutputList.cxx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputList.hxx" +#include "OutputAPI.hxx" +#include "output/AlsaOutputPlugin.hxx" +#include "output/AoOutputPlugin.hxx" +#include "output/FifoOutputPlugin.hxx" +#include "output/HttpdOutputPlugin.hxx" +#include "output/JackOutputPlugin.hxx" +#include "output/NullOutputPlugin.hxx" +#include "output/OpenALOutputPlugin.hxx" +#include "output/OssOutputPlugin.hxx" +#include "output/OSXOutputPlugin.hxx" +#include "output/PipeOutputPlugin.hxx" +#include "output/PulseOutputPlugin.hxx" +#include "output/RecorderOutputPlugin.hxx" +#include "output/RoarOutputPlugin.hxx" +#include "output/ShoutOutputPlugin.hxx" +#include "output/SolarisOutputPlugin.hxx" +#include "output/WinmmOutputPlugin.hxx" + +#include <string.h> + +const struct audio_output_plugin *const audio_output_plugins[] = { +#ifdef HAVE_SHOUT + &shout_output_plugin, +#endif + &null_output_plugin, +#ifdef HAVE_FIFO + &fifo_output_plugin, +#endif +#ifdef ENABLE_PIPE_OUTPUT + &pipe_output_plugin, +#endif +#ifdef HAVE_ALSA + &alsa_output_plugin, +#endif +#ifdef HAVE_ROAR + &roar_output_plugin, +#endif +#ifdef HAVE_AO + &ao_output_plugin, +#endif +#ifdef HAVE_OSS + &oss_output_plugin, +#endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif +#ifdef HAVE_OSX + &osx_output_plugin, +#endif +#ifdef ENABLE_SOLARIS_OUTPUT + &solaris_output_plugin, +#endif +#ifdef HAVE_PULSE + &pulse_output_plugin, +#endif +#ifdef HAVE_JACK + &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 + NULL +}; + +const struct audio_output_plugin * +audio_output_plugin_get(const char *name) +{ + audio_output_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} diff --git a/src/OutputList.hxx b/src/OutputList.hxx new file mode 100644 index 000000000..b7716c67e --- /dev/null +++ b/src/OutputList.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX + +extern const struct audio_output_plugin *const audio_output_plugins[]; + +const struct audio_output_plugin * +audio_output_plugin_get(const char *name); + +#define audio_output_plugins_for_each(plugin) \ + for (const struct audio_output_plugin *plugin, \ + *const*output_plugin_iterator = &audio_output_plugins[0]; \ + (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator) + +#endif diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx new file mode 100644 index 000000000..038523ad9 --- /dev/null +++ b/src/OutputPlugin.cxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputPlugin.hxx" +#include "OutputInternal.hxx" + +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const config_param ¶m, + Error &error) +{ + assert(plugin != NULL); + assert(plugin->init != NULL); + + return plugin->init(param, error); +} + +void +ao_plugin_finish(struct audio_output *ao) +{ + ao->plugin->finish(ao); +} + +bool +ao_plugin_enable(struct audio_output *ao, Error &error_r) +{ + return ao->plugin->enable != NULL + ? ao->plugin->enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(struct audio_output *ao) +{ + if (ao->plugin->disable != NULL) + ao->plugin->disable(ao); +} + +bool +ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + return ao->plugin->open(ao, audio_format, error); +} + +void +ao_plugin_close(struct audio_output *ao) +{ + ao->plugin->close(ao); +} + +unsigned +ao_plugin_delay(struct audio_output *ao) +{ + return ao->plugin->delay != NULL + ? ao->plugin->delay(ao) + : 0; +} + +void +ao_plugin_send_tag(struct audio_output *ao, const Tag *tag) +{ + if (ao->plugin->send_tag != NULL) + ao->plugin->send_tag(ao, tag); +} + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + return ao->plugin->play(ao, chunk, size, error); +} + +void +ao_plugin_drain(struct audio_output *ao) +{ + if (ao->plugin->drain != NULL) + ao->plugin->drain(ao); +} + +void +ao_plugin_cancel(struct audio_output *ao) +{ + if (ao->plugin->cancel != NULL) + ao->plugin->cancel(ao); +} + +bool +ao_plugin_pause(struct audio_output *ao) +{ + return ao->plugin->pause != NULL && ao->plugin->pause(ao); +} diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx new file mode 100644 index 000000000..20fd74453 --- /dev/null +++ b/src/OutputPlugin.hxx @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_PLUGIN_HXX +#define MPD_OUTPUT_PLUGIN_HXX + +#include "gcc.h" + +#include <stddef.h> + +struct config_param; +struct AudioFormat; +struct Tag; +class Error; + +/** + * A plugin which controls an audio output device. + */ +struct audio_output_plugin { + /** + * the plugin's name + */ + const char *name; + + /** + * Test if this plugin can provide a default output, in case + * none has been configured. This method is optional. + */ + bool (*test_default_device)(void); + + /** + * Configure and initialize the device, but do not open it + * yet. + * + * @param param the configuration section, or NULL if there is + * no configuration + * @param error location to store the error occurring, or NULL + * to ignore errors + * @return NULL on error, or an opaque pointer to the plugin's + * data + */ + struct audio_output *(*init)(const config_param ¶m, + Error &error); + + /** + * Free resources allocated by this device. + */ + void (*finish)(struct audio_output *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 occurring, or + * NULL to ignore errors + * @return true on success, false on error + */ + bool (*enable)(struct audio_output *data, Error &error); + + /** + * Disables the device. It is closed before this method is + * called. + */ + void (*disable)(struct audio_output *data); + + /** + * Really open the device. + * + * @param audio_format the audio format in which data is going + * to be delivered; may be modified by the plugin + * @param error location to store the error occurring, or NULL + * to ignore errors + */ + bool (*open)(struct audio_output *data, AudioFormat &audio_format, + Error &error); + + /** + * Close the device. + */ + void (*close)(struct audio_output *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)(struct audio_output *data); + + /** + * Display metadata for the next chunk. Optional method, + * because not all devices can display metadata. + */ + void (*send_tag)(struct audio_output *data, const Tag *tag); + + /** + * Play a chunk of audio data. + * + * @param error location to store the error occurring, or NULL + * to ignore errors + * @return the number of bytes played, or 0 on error + */ + size_t (*play)(struct audio_output *data, + const void *chunk, size_t size, + Error &error); + + /** + * Wait until the device has finished playing. + */ + void (*drain)(struct audio_output *data); + + /** + * Try to cancel data which may still be in the device's + * buffers. + */ + void (*cancel)(struct audio_output *data); + + /** + * Pause the device. If supported, it may perform a special + * action, which keeps the device open, but does not play + * anything. Output plugins like "shout" might want to play + * silence during pause, so their clients won't be + * disconnected. Plugins which do not support pausing will + * simply be closed, and have to be reopened when unpaused. + * + * @return false on error (output will be closed then), true + * for continue to pause + */ + bool (*pause)(struct audio_output *data); + + /** + * The mixer plugin associated with this output plugin. This + * may be NULL if no mixer plugin is implemented. When + * created, this mixer plugin gets the same #config_param as + * this audio output device. + */ + const struct mixer_plugin *mixer_plugin; +}; + +static inline bool +ao_plugin_test_default_device(const struct audio_output_plugin *plugin) +{ + return plugin->test_default_device != NULL + ? plugin->test_default_device() + : false; +} + +gcc_malloc +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const config_param ¶m, + Error &error); + +void +ao_plugin_finish(struct audio_output *ao); + +bool +ao_plugin_enable(struct audio_output *ao, Error &error); + +void +ao_plugin_disable(struct audio_output *ao); + +bool +ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error); + +void +ao_plugin_close(struct audio_output *ao); + +gcc_pure +unsigned +ao_plugin_delay(struct audio_output *ao); + +void +ao_plugin_send_tag(struct audio_output *ao, const Tag *tag); + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error); + +void +ao_plugin_drain(struct audio_output *ao); + +void +ao_plugin_cancel(struct audio_output *ao); + +bool +ao_plugin_pause(struct audio_output *ao); + +#endif diff --git a/src/OutputPrint.cxx b/src/OutputPrint.cxx new file mode 100644 index 000000000..4e1cf9ced --- /dev/null +++ b/src/OutputPrint.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#include "config.h" +#include "OutputPrint.hxx" +#include "OutputAll.hxx" +#include "OutputInternal.hxx" +#include "Client.hxx" + +void +printAudioDevices(Client *client) +{ + const unsigned n = audio_output_count(); + + for (unsigned i = 0; i < n; ++i) { + const struct audio_output *ao = audio_output_get(i); + + client_printf(client, + "outputid: %i\n" + "outputname: %s\n" + "outputenabled: %i\n", + i, ao->name, ao->enabled); + } +} diff --git a/src/OutputPrint.hxx b/src/OutputPrint.hxx new file mode 100644 index 000000000..78717d0af --- /dev/null +++ b/src/OutputPrint.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX + +class Client; + +void +printAudioDevices(Client *client); + +#endif diff --git a/src/OutputState.cxx b/src/OutputState.cxx new file mode 100644 index 000000000..a3650413c --- /dev/null +++ b/src/OutputState.cxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#include "config.h" +#include "OutputState.hxx" +#include "OutputAll.hxx" +#include "OutputInternal.hxx" +#include "OutputError.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#define AUDIO_DEVICE_STATE "audio_device_state:" + +unsigned audio_output_state_version; + +void +audio_output_state_save(FILE *fp) +{ + unsigned n = audio_output_count(); + + assert(n > 0); + + for (unsigned i = 0; i < n; ++i) { + const struct audio_output *ao = audio_output_get(i); + + fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", + ao->enabled, ao->name); + } +} + +bool +audio_output_state_read(const char *line) +{ + long value; + char *endptr; + const char *name; + struct audio_output *ao; + + if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) + return false; + + line += sizeof(AUDIO_DEVICE_STATE) - 1; + + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; + + if (value != 0) + /* state is "enabled": no-op */ + return true; + + name = endptr + 1; + ao = audio_output_find(name); + if (ao == NULL) { + FormatDebug(output_domain, + "Ignoring device state for '%s'", name); + return true; + } + + ao->enabled = false; + return true; +} + +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; +} diff --git a/src/OutputState.hxx b/src/OutputState.hxx new file mode 100644 index 000000000..5ab765ba8 --- /dev/null +++ b/src/OutputState.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX + +#include <stdio.h> + +bool +audio_output_state_read(const char *line); + +void +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/OutputThread.cxx b/src/OutputThread.cxx new file mode 100644 index 000000000..b1c670afc --- /dev/null +++ b/src/OutputThread.cxx @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputThread.hxx" +#include "OutputInternal.hxx" +#include "OutputAPI.hxx" +#include "OutputError.hxx" +#include "pcm/PcmMix.hxx" +#include "notify.hxx" +#include "FilterInternal.hxx" +#include "filter/ConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "gcc.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static void ao_command_finished(struct audio_output *ao) +{ + assert(ao->command != AO_COMMAND_NONE); + ao->command = AO_COMMAND_NONE; + + ao->mutex.unlock(); + audio_output_client_notify.Signal(); + ao->mutex.lock(); +} + +static bool +ao_enable(struct audio_output *ao) +{ + Error error; + bool success; + + if (ao->really_enabled) + return true; + + ao->mutex.unlock(); + success = ao_plugin_enable(ao, error); + ao->mutex.lock(); + if (!success) { + FormatError(error, + "Failed to enable \"%s\" [%s]", + ao->name, ao->plugin->name); + 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; + + ao->mutex.unlock(); + ao_plugin_disable(ao); + ao->mutex.lock(); + } +} + +static AudioFormat +ao_filter_open(struct audio_output *ao, AudioFormat &format, + Error &error_r) +{ + assert(format.IsValid()); + + /* the replay_gain filter cannot fail here */ + if (ao->replay_gain_filter != NULL) + ao->replay_gain_filter->Open(format, error_r); + if (ao->other_replay_gain_filter != NULL) + ao->other_replay_gain_filter->Open(format, error_r); + + const AudioFormat af = ao->filter->Open(format, error_r); + if (!af.IsDefined()) { + if (ao->replay_gain_filter != NULL) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != NULL) + ao->other_replay_gain_filter->Close(); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != NULL) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != NULL) + ao->other_replay_gain_filter->Close(); + + ao->filter->Close(); +} + +static void +ao_open(struct audio_output *ao) +{ + bool success; + Error error; + struct audio_format_string af_string; + + assert(!ao->open); + assert(ao->pipe != NULL); + assert(ao->chunk == NULL); + assert(ao->in_audio_format.IsValid()); + + 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 */ + + const AudioFormat filter_audio_format = + ao_filter_open(ao, ao->in_audio_format, error); + if (!filter_audio_format.IsDefined()) { + FormatError(error, "Failed to open filter for \"%s\" [%s]", + ao->name, ao->plugin->name); + + ao->fail_timer = g_timer_new(); + return; + } + + assert(filter_audio_format.IsValid()); + + ao->out_audio_format = filter_audio_format; + ao->out_audio_format.ApplyMask(ao->config_audio_format); + + ao->mutex.unlock(); + success = ao_plugin_open(ao, ao->out_audio_format, error); + ao->mutex.lock(); + + assert(!ao->open); + + if (!success) { + FormatError(error, "Failed to open \"%s\" [%s]", + ao->name, ao->plugin->name); + + ao_filter_close(ao); + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, ao->out_audio_format); + + ao->open = true; + + FormatDebug(output_domain, + "opened plugin=%s name=\"%s\" audio_format=%s", + ao->plugin->name, ao->name, + audio_format_to_string(ao->out_audio_format, &af_string)); + + if (ao->in_audio_format != ao->out_audio_format) + FormatDebug(output_domain, "converting from %s", + audio_format_to_string(ao->in_audio_format, + &af_string)); +} + +static void +ao_close(struct audio_output *ao, bool drain) +{ + assert(ao->open); + + ao->pipe = NULL; + + ao->chunk = NULL; + ao->open = false; + + ao->mutex.unlock(); + + if (drain) + ao_plugin_drain(ao); + else + ao_plugin_cancel(ao); + + ao_plugin_close(ao); + ao_filter_close(ao); + + ao->mutex.lock(); + + FormatDebug(output_domain, "closed plugin=%s name=\"%s\"", + ao->plugin->name, ao->name); +} + +static void +ao_reopen_filter(struct audio_output *ao) +{ + Error error; + + ao_filter_close(ao); + const AudioFormat filter_audio_format = + ao_filter_open(ao, ao->in_audio_format, error); + if (!filter_audio_format.IsDefined()) { + FormatError(error, + "Failed to open filter for \"%s\" [%s]", + ao->name, ao->plugin->name); + + /* 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(); + + ao->mutex.unlock(); + ao_plugin_close(ao); + ao->mutex.lock(); + + return; + } + + convert_filter_set(ao->convert_filter, ao->out_audio_format); +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!ao->config_audio_format.IsFullyDefined()) { + if (ao->open) { + const MusicPipe *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; + ao->out_audio_format.ApplyMask(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); + if (delay == 0) + return true; + + (void)ao->cond.timed_wait(ao->mutex, delay); + + if (ao->command != AO_COMMAND_NONE) + return false; + } +} + +static const void * +ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p, + size_t *length_r) +{ + assert(chunk != NULL); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); + + const void *data = chunk->data; + size_t length = chunk->length; + + (void)ao; + + assert(length % ao->in_audio_format.GetFrameSize() == 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; + } + + Error error; + data = replay_gain_filter->FilterPCM(data, length, + &length, error); + if (data == NULL) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin->name); + return NULL; + } + } + + *length_r = length; + return data; +} + +static const void * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + size_t length; + const void *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 void *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; + + void *dest = ao->cross_fade_buffer.Get(other_length); + memcpy(dest, other_data, other_length); + if (!pcm_mix(dest, data, length, + ao->in_audio_format.format, + 1.0 - chunk->mix_ratio)) { + FormatError(output_domain, + "Cannot cross-fade format %s", + sample_format_to_string(ao->in_audio_format.format)); + return NULL; + } + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + Error error; + data = ao->filter->FilterPCM(data, length, &length, error); + if (data == NULL) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin->name); + return NULL; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + assert(ao != NULL); + assert(ao->filter != NULL); + + if (ao->tags && gcc_unlikely(chunk->tag != NULL)) { + ao->mutex.unlock(); + ao_plugin_send_tag(ao, chunk->tag); + ao->mutex.lock(); + } + + size_t size; +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + size = 0; +#endif + const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); + if (data == NULL) { + ao_close(ao, false); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer = g_timer_new(); + return false; + } + + Error error; + + while (size > 0 && ao->command == AO_COMMAND_NONE) { + size_t nbytes; + + if (!ao_wait(ao)) + break; + + ao->mutex.unlock(); + nbytes = ao_plugin_play(ao, data, size, error); + ao->mutex.lock(); + if (nbytes == 0) { + /* play()==0 means failure */ + FormatError(error, "\"%s\" [%s] failed to play", + ao->name, ao->plugin->name); + + ao_close(ao, false); + + /* don't automatically reopen this device for + 10 seconds */ + assert(ao->fail_timer == NULL); + ao->fail_timer = g_timer_new(); + + return false; + } + + assert(nbytes <= size); + assert(nbytes % ao->out_audio_format.GetFrameSize() == 0); + + data += nbytes; + size -= nbytes; + } + + return true; +} + +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 */ + : ao->pipe->Peek(); +} + +/** + * 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); + + 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; + + success = ao_play_chunk(ao, chunk); + if (!success) { + assert(ao->chunk == NULL); + break; + } + + assert(ao->chunk == chunk); + chunk = chunk->next; + } + + ao->chunk_finished = true; + + ao->mutex.unlock(); + ao->player_control->LockSignal(); + ao->mutex.lock(); + + return true; +} + +static void ao_pause(struct audio_output *ao) +{ + bool ret; + + ao->mutex.unlock(); + ao_plugin_cancel(ao); + ao->mutex.lock(); + + ao->pause = true; + ao_command_finished(ao); + + do { + if (!ao_wait(ao)) + break; + + ao->mutex.unlock(); + ret = ao_plugin_pause(ao); + ao->mutex.lock(); + + if (!ret) { + ao_close(ao, false); + break; + } + } while (ao->command == AO_COMMAND_NONE); + + ao->pause = false; +} + +static gpointer audio_output_task(gpointer arg) +{ + struct audio_output *ao = (struct audio_output *)arg; + + ao->mutex.lock(); + + while (1) { + switch (ao->command) { + case AO_COMMAND_NONE: + break; + + case AO_COMMAND_ENABLE: + ao_enable(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_DISABLE: + ao_disable(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_OPEN: + ao_open(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_REOPEN: + ao_reopen(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_CLOSE: + assert(ao->open); + assert(ao->pipe != NULL); + + ao_close(ao, false); + ao_command_finished(ao); + break; + + case AO_COMMAND_PAUSE: + if (!ao->open) { + /* the output has failed after + audio_output_all_pause() has + submitted the PAUSE command; bail + out */ + ao_command_finished(ao); + break; + } + + ao_pause(ao); + /* don't "break" here: this might cause + ao_play() to be called when command==CLOSE + ends the paused state - "continue" checks + the new command first */ + continue; + + case AO_COMMAND_DRAIN: + if (ao->open) { + assert(ao->chunk == NULL); + assert(ao->pipe->Peek() == nullptr); + + ao->mutex.unlock(); + ao_plugin_drain(ao); + ao->mutex.lock(); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_CANCEL: + ao->chunk = NULL; + + if (ao->open) { + ao->mutex.unlock(); + ao_plugin_cancel(ao); + ao->mutex.lock(); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_KILL: + ao->chunk = NULL; + ao_command_finished(ao); + ao->mutex.unlock(); + return NULL; + } + + if (ao->open && ao->allow_play && ao_play(ao)) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; + + if (ao->command == AO_COMMAND_NONE) + ao->cond.wait(ao->mutex); + } +} + +void audio_output_thread_start(struct audio_output *ao) +{ + assert(ao->command == AO_COMMAND_NONE); + +#if GLIB_CHECK_VERSION(2,32,0) + ao->thread = g_thread_new("output", audio_output_task, ao); +#else + GError *e = nullptr; + if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) + FatalError("Failed to spawn output task", e); +#endif +} diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx new file mode 100644 index 000000000..1a7932162 --- /dev/null +++ b/src/OutputThread.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_THREAD_HXX +#define MPD_OUTPUT_THREAD_HXX + +struct audio_output; + +void audio_output_thread_start(struct audio_output *ao); + +#endif diff --git a/src/Page.cxx b/src/Page.cxx new file mode 100644 index 000000000..bf30376e4 --- /dev/null +++ b/src/Page.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Page.hxx" + +#include <glib.h> + +#include <new> + +#include <assert.h> +#include <string.h> + +Page * +Page::Create(size_t size) +{ + void *p = g_malloc(sizeof(Page) + size - + sizeof(Page::data)); + return ::new(p) Page(size); +} + +Page * +Page::Copy(const void *data, size_t size) +{ + assert(data != nullptr); + + Page *page = Create(size); + memcpy(page->data, data, size); + return page; +} + +Page * +Page::Concat(const Page &a, const Page &b) +{ + Page *page = Create(a.size + b.size); + + memcpy(page->data, a.data, a.size); + memcpy(page->data + a.size, b.data, b.size); + + return page; +} + +bool +Page::Unref() +{ + bool unused = ref.Decrement(); + + if (unused) { + this->Page::~Page(); + g_free(this); + } + + return unused; +} diff --git a/src/Page.hxx b/src/Page.hxx new file mode 100644 index 000000000..2bc9a6ac5 --- /dev/null +++ b/src/Page.hxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This is a library which manages reference counted buffers. + */ + +#ifndef MPD_PAGE_HXX +#define MPD_PAGE_HXX + +#include "util/RefCount.hxx" + +#include <algorithm> + +#include <stddef.h> + +/** + * A dynamically allocated buffer which keeps track of its reference + * count. This is useful for passing buffers around, when several + * instances hold references to one buffer. + */ +class Page { + /** + * The number of references to this buffer. This library uses + * atomic functions to access it, i.e. no locks are required. + * As soon as this attribute reaches zero, the buffer is + * freed. + */ + RefCount ref; + +public: + /** + * The size of this buffer in bytes. + */ + const size_t size; + + /** + * Dynamic array containing the buffer data. + */ + unsigned char data[sizeof(long)]; + +protected: + Page(size_t _size):size(_size) {} + ~Page() = default; + + /** + * Allocates a new #Page object, without filling the data + * element. + */ + static Page *Create(size_t size); + +public: + /** + * Creates a new #page object, and copies data from the + * specified buffer. It is initialized with a reference count + * of 1. + * + * @param data the source buffer + * @param size the size of the source buffer + */ + static Page *Copy(const void *data, size_t size); + + /** + * Concatenates two pages to a new page. + * + * @param a the first page + * @param b the second page, which is appended + */ + static Page *Concat(const Page &a, const Page &b); + + /** + * Increases the reference counter. + */ + void Ref() { + ref.Increment(); + } + + /** + * Decreases the reference counter. If it reaches zero, the #page is + * freed. + * + * @return true if the #page has been freed + */ + bool Unref(); +}; + +#endif diff --git a/src/Partition.hxx b/src/Partition.hxx new file mode 100644 index 000000000..0d5017985 --- /dev/null +++ b/src/Partition.hxx @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PARTITION_HXX +#define MPD_PARTITION_HXX + +#include "Playlist.hxx" +#include "PlayerControl.hxx" + +struct Instance; + +/** + * A partition of the Music Player Daemon. It is a separate unit with + * a playlist, a player, outputs etc. + */ +struct Partition { + Instance &instance; + + struct playlist playlist; + + player_control pc; + + Partition(Instance &_instance, + unsigned max_length, + unsigned buffer_chunks, + unsigned buffered_before_play) + :instance(_instance), playlist(max_length), + pc(buffer_chunks, buffered_before_play) { + } + + void ClearQueue() { + playlist.Clear(pc); + } + + enum playlist_result AppendFile(const char *path_utf8, + unsigned *added_id=nullptr) { + return playlist.AppendFile(pc, path_utf8, added_id); + } + + enum playlist_result AppendURI(const char *uri_utf8, + unsigned *added_id=nullptr) { + return playlist.AppendURI(pc, uri_utf8, added_id); + } + + enum playlist_result DeletePosition(unsigned position) { + return playlist.DeletePosition(pc, position); + } + + enum playlist_result DeleteId(unsigned id) { + return playlist.DeleteId(pc, id); + } + + /** + * 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 DeleteRange(unsigned start, unsigned end) { + return playlist.DeleteRange(pc, start, end); + } + + void DeleteSong(const Song &song) { + playlist.DeleteSong(pc, song); + } + + void Shuffle(unsigned start, unsigned end) { + playlist.Shuffle(pc, start, end); + } + + enum playlist_result MoveRange(unsigned start, unsigned end, int to) { + return playlist.MoveRange(pc, start, end, to); + } + + enum playlist_result MoveId(unsigned id, int to) { + return playlist.MoveId(pc, id, to); + } + + enum playlist_result SwapPositions(unsigned song1, unsigned song2) { + return playlist.SwapPositions(pc, song1, song2); + } + + enum playlist_result SwapIds(unsigned id1, unsigned id2) { + return playlist.SwapIds(pc, id1, id2); + } + + enum playlist_result SetPriorityRange(unsigned start_position, + unsigned end_position, + uint8_t priority) { + return playlist.SetPriorityRange(pc, + start_position, end_position, + priority); + } + + enum playlist_result SetPriorityId(unsigned song_id, + uint8_t priority) { + return playlist.SetPriorityId(pc, song_id, priority); + } + + void Stop() { + playlist.Stop(pc); + } + + enum playlist_result PlayPosition(int position) { + return playlist.PlayPosition(pc, position); + } + + enum playlist_result PlayId(int id) { + return playlist.PlayId(pc, id); + } + + void PlayNext() { + return playlist.PlayNext(pc); + } + + void PlayPrevious() { + return playlist.PlayPrevious(pc); + } + + enum playlist_result SeekSongPosition(unsigned song_position, + float seek_time) { + return playlist.SeekSongPosition(pc, song_position, seek_time); + } + + enum playlist_result SeekSongId(unsigned song_id, float seek_time) { + return playlist.SeekSongId(pc, song_id, seek_time); + } + + enum playlist_result SeekCurrent(float seek_time, bool relative) { + return playlist.SeekCurrent(pc, seek_time, relative); + } + + void SetRepeat(bool new_value) { + playlist.SetRepeat(pc, new_value); + } + + bool GetRandom() const { + return playlist.GetRandom(); + } + + void SetRandom(bool new_value) { + playlist.SetRandom(pc, new_value); + } + + void SetSingle(bool new_value) { + playlist.SetSingle(pc, new_value); + } + + void SetConsume(bool new_value) { + playlist.SetConsume(new_value); + } +}; + +#endif diff --git a/src/Permission.cxx b/src/Permission.cxx new file mode 100644 index 000000000..258ace60a --- /dev/null +++ b/src/Permission.cxx @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Permission.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "system/FatalError.hxx" + +#include <map> +#include <string> + +#include <glib.h> + +#include <string.h> + +#define PERMISSION_PASSWORD_CHAR '@' +#define PERMISSION_SEPERATOR "," + +#define PERMISSION_READ_STRING "read" +#define PERMISSION_ADD_STRING "add" +#define PERMISSION_CONTROL_STRING "control" +#define PERMISSION_ADMIN_STRING "admin" + +static std::map<std::string, unsigned> permission_passwords; + +static unsigned permission_default; + +static unsigned parsePermissions(const char *string) +{ + unsigned permission = 0; + gchar **tokens; + + if (!string) + return 0; + + tokens = g_strsplit(string, PERMISSION_SEPERATOR, 0); + for (unsigned i = 0; tokens[i] != NULL; ++i) { + char *temp = tokens[i]; + + if (strcmp(temp, PERMISSION_READ_STRING) == 0) { + permission |= PERMISSION_READ; + } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) { + permission |= PERMISSION_ADD; + } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) { + permission |= PERMISSION_CONTROL; + } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { + permission |= PERMISSION_ADMIN; + } else { + FormatFatalError("unknown permission \"%s\"", temp); + } + } + + g_strfreev(tokens); + + return permission; +} + +void initPermissions(void) +{ + char *password; + unsigned permission; + const struct config_param *param; + + permission_default = PERMISSION_READ | PERMISSION_ADD | + PERMISSION_CONTROL | PERMISSION_ADMIN; + + param = config_get_next_param(CONF_PASSWORD, NULL); + + if (param) { + permission_default = 0; + + do { + const char *separator = + strchr(param->value, PERMISSION_PASSWORD_CHAR); + + if (separator == NULL) + FormatFatalError("\"%c\" not found in password string " + "\"%s\", line %i", + PERMISSION_PASSWORD_CHAR, + param->value, param->line); + + password = g_strndup(param->value, + separator - param->value); + + permission = parsePermissions(separator + 1); + + permission_passwords.insert(std::make_pair(password, + permission)); + } while ((param = config_get_next_param(CONF_PASSWORD, param))); + } + + param = config_get_param(CONF_DEFAULT_PERMS); + + if (param) + permission_default = parsePermissions(param->value); +} + +int getPermissionFromPassword(char const* password, unsigned* permission) +{ + auto i = permission_passwords.find(password); + if (i == permission_passwords.end()) + return -1; + + *permission = i->second; + return 0; +} + +unsigned getDefaultPermissions(void) +{ + return permission_default; +} diff --git a/src/Permission.hxx b/src/Permission.hxx new file mode 100644 index 000000000..4ff3850e0 --- /dev/null +++ b/src/Permission.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PERMISSION_HXX +#define MPD_PERMISSION_HXX + +#define PERMISSION_NONE 0 +#define PERMISSION_READ 1 +#define PERMISSION_ADD 2 +#define PERMISSION_CONTROL 4 +#define PERMISSION_ADMIN 8 + + +int getPermissionFromPassword(char const* password, unsigned* permission); + +unsigned getDefaultPermissions(void); + +void initPermissions(void); + +#endif diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx new file mode 100644 index 000000000..1a2580ee3 --- /dev/null +++ b/src/PlayerCommands.cxx @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlayerCommands.hxx" +#include "CommandError.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "UpdateGlue.hxx" +#include "ClientInternal.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "Partition.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" +#include "AudioFormat.hxx" +#include "ReplayGainConfig.hxx" + +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_SINGLE "single" +#define COMMAND_STATUS_CONSUME "consume" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_NEXTSONG "nextsong" +#define COMMAND_STATUS_NEXTSONGID "nextsongid" +#define COMMAND_STATUS_TIME "time" +#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" + +enum command_return +handle_play(Client *client, int argc, char *argv[]) +{ + int song = -1; + + if (argc == 2 && !check_int(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + enum playlist_result result = client->partition.PlayPosition(song); + return print_playlist_result(client, result); +} + +enum command_return +handle_playid(Client *client, int argc, char *argv[]) +{ + int id = -1; + + if (argc == 2 && !check_int(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.PlayId(id); + return print_playlist_result(client, result); +} + +enum command_return +handle_stop(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client->partition.Stop(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_currentsong(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + playlist_print_current(client, &client->playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_pause(Client *client, + int argc, char *argv[]) +{ + if (argc == 2) { + bool pause_flag; + if (!check_bool(client, &pause_flag, argv[1])) + return COMMAND_RETURN_ERROR; + + client->player_control->SetPause(pause_flag); + } else + client->player_control->Pause(); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_status(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + const char *state = NULL; + int updateJobId; + int song; + + const auto player_status = client->player_control->GetStatus(); + + switch (player_status.state) { + case PlayerState::STOP: + state = "stop"; + break; + case PlayerState::PAUSE: + state = "pause"; + break; + case PlayerState::PLAY: + state = "play"; + break; + } + + const playlist &playlist = client->playlist; + client_printf(client, + "volume: %i\n" + COMMAND_STATUS_REPEAT ": %i\n" + COMMAND_STATUS_RANDOM ": %i\n" + COMMAND_STATUS_SINGLE ": %i\n" + COMMAND_STATUS_CONSUME ": %i\n" + 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(), + playlist.GetRepeat(), + playlist.GetRandom(), + playlist.GetSingle(), + playlist.GetConsume(), + (unsigned long)playlist.GetVersion(), + playlist.GetLength(), + (int)(client->player_control->GetCrossFade() + 0.5), + client->player_control->GetMixRampDb(), + client->player_control->GetMixRampDelay(), + state); + + song = playlist.GetCurrentPosition(); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_SONG ": %i\n" + COMMAND_STATUS_SONGID ": %u\n", + song, playlist.PositionToId(song)); + } + + if (player_status.state != PlayerState::STOP) { + client_printf(client, + COMMAND_STATUS_TIME ": %i:%i\n" + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate); + + if (player_status.audio_format.IsDefined()) { + struct audio_format_string af_string; + + client_printf(client, + COMMAND_STATUS_AUDIO ": %s\n", + audio_format_to_string(player_status.audio_format, + &af_string)); + } + } + + if ((updateJobId = isUpdatingDB())) { + client_printf(client, + COMMAND_STATUS_UPDATING_DB ": %i\n", + updateJobId); + } + + char *error = client->player_control->GetErrorMessage(); + if (error != NULL) { + client_printf(client, + COMMAND_STATUS_ERROR ": %s\n", + error); + g_free(error); + } + + song = playlist.GetNextPosition(); + if (song >= 0) { + client_printf(client, + COMMAND_STATUS_NEXTSONG ": %i\n" + COMMAND_STATUS_NEXTSONGID ": %u\n", + song, playlist.PositionToId(song)); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_next(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + playlist &playlist = client->playlist; + + /* single mode is not considered when this is user who + * wants to change song. */ + const bool single = playlist.queue.single; + playlist.queue.single = false; + + client->partition.PlayNext(); + + playlist.queue.single = single; + return COMMAND_RETURN_OK; +} + +enum command_return +handle_previous(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client->partition.PlayPrevious(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_repeat(Client *client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetRepeat(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_single(Client *client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetSingle(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_consume(Client *client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetConsume(status); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_random(Client *client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.SetRandom(status); + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->partition.GetRandom())); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clearerror(gcc_unused Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client->player_control->ClearError(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_seek(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned song, seek_time; + + if (!check_unsigned(client, &song, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekSongPosition(song, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekid(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned id, seek_time; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekSongId(id, seek_time); + return print_playlist_result(client, result); +} + +enum command_return +handle_seekcur(Client *client, gcc_unused int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SeekCurrent(seek_time, relative); + return print_playlist_result(client, result); +} + +enum command_return +handle_crossfade(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned xfade_time; + + if (!check_unsigned(client, &xfade_time, argv[1])) + return COMMAND_RETURN_ERROR; + client->player_control->SetCrossFade(xfade_time); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_mixrampdb(Client *client, gcc_unused int argc, char *argv[]) +{ + float db; + + if (!check_float(client, &db, argv[1])) + return COMMAND_RETURN_ERROR; + client->player_control->SetMixRampDb(db); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_mixrampdelay(Client *client, gcc_unused int argc, char *argv[]) +{ + float delay_secs; + + if (!check_float(client, &delay_secs, argv[1])) + return COMMAND_RETURN_ERROR; + client->player_control->SetMixRampDelay(delay_secs); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_mode(Client *client, + gcc_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; + } + + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client->playlist.queue.random)); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_replay_gain_status(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client_printf(client, "replay_gain_mode: %s\n", + replay_gain_get_mode_string()); + return COMMAND_RETURN_OK; +} diff --git a/src/PlayerCommands.hxx b/src/PlayerCommands.hxx new file mode 100644 index 000000000..a2fed5853 --- /dev/null +++ b/src/PlayerCommands.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYER_COMMANDS_HXX +#define MPD_PLAYER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_play(Client *client, int argc, char *argv[]); + +enum command_return +handle_playid(Client *client, int argc, char *argv[]); + +enum command_return +handle_stop(Client *client, int argc, char *argv[]); + +enum command_return +handle_currentsong(Client *client, int argc, char *argv[]); + +enum command_return +handle_pause(Client *client, int argc, char *argv[]); + +enum command_return +handle_status(Client *client, int argc, char *argv[]); + +enum command_return +handle_next(Client *client, int argc, char *argv[]); + +enum command_return +handle_previous(Client *client, int argc, char *avg[]); + +enum command_return +handle_repeat(Client *client, int argc, char *argv[]); + +enum command_return +handle_single(Client *client, int argc, char *argv[]); + +enum command_return +handle_consume(Client *client, int argc, char *argv[]); + +enum command_return +handle_random(Client *client, int argc, char *argv[]); + +enum command_return +handle_clearerror(Client *client, int argc, char *argv[]); + +enum command_return +handle_seek(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekid(Client *client, int argc, char *argv[]); + +enum command_return +handle_seekcur(Client *client, int argc, char *argv[]); + +enum command_return +handle_crossfade(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdb(Client *client, int argc, char *argv[]); + +enum command_return +handle_mixrampdelay(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_mode(Client *client, int argc, char *argv[]); + +enum command_return +handle_replay_gain_status(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx new file mode 100644 index 000000000..56b44f1f8 --- /dev/null +++ b/src/PlayerControl.cxx @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlayerControl.hxx" +#include "Idle.hxx" +#include "Song.hxx" +#include "DecoderControl.hxx" + +#include <cmath> + +#include <assert.h> + +player_control::player_control(unsigned _buffer_chunks, + unsigned _buffered_before_play) + :buffer_chunks(_buffer_chunks), + buffered_before_play(_buffered_before_play), + thread(nullptr), + command(PlayerCommand::NONE), + state(PlayerState::STOP), + error_type(PlayerError::NONE), + next_song(nullptr), + cross_fade_seconds(0), + mixramp_db(0), +#if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_C99_MATH_TR1) + /* workaround: on MacPorts, this option is disabled on gcc47, + and therefore std::nanf() is not available */ + mixramp_delay_seconds(nanf("")), +#else + mixramp_delay_seconds(std::nanf("")), +#endif + total_play_time(0), + border_pause(false) +{ +} + +player_control::~player_control() +{ + if (next_song != nullptr) + next_song->Free(); +} + +void +player_control::Play(Song *song) +{ + assert(song != NULL); + + Lock(); + + if (state != PlayerState::STOP) + SynchronousCommand(PlayerCommand::STOP); + + assert(next_song == nullptr); + + EnqueueSongLocked(song); + + assert(next_song == nullptr); + + Unlock(); +} + +void +player_control::Cancel() +{ + LockSynchronousCommand(PlayerCommand::CANCEL); + assert(next_song == NULL); +} + +void +player_control::Stop() +{ + LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO); + assert(next_song == nullptr); + + idle_add(IDLE_PLAYER); +} + +void +player_control::UpdateAudio() +{ + LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO); +} + +void +player_control::Kill() +{ + assert(thread != NULL); + + LockSynchronousCommand(PlayerCommand::EXIT); + g_thread_join(thread); + thread = NULL; + + idle_add(IDLE_PLAYER); +} + +void +player_control::PauseLocked() +{ + if (state != PlayerState::STOP) { + SynchronousCommand(PlayerCommand::PAUSE); + idle_add(IDLE_PLAYER); + } +} + +void +player_control::Pause() +{ + Lock(); + PauseLocked(); + Unlock(); +} + +void +player_control::SetPause(bool pause_flag) +{ + Lock(); + + switch (state) { + case PlayerState::STOP: + break; + + case PlayerState::PLAY: + if (pause_flag) + PauseLocked(); + break; + + case PlayerState::PAUSE: + if (!pause_flag) + PauseLocked(); + break; + } + + Unlock(); +} + +void +player_control::SetBorderPause(bool _border_pause) +{ + Lock(); + border_pause = _border_pause; + Unlock(); +} + +player_status +player_control::GetStatus() +{ + player_status status; + + Lock(); + SynchronousCommand(PlayerCommand::REFRESH); + + status.state = state; + + if (state != PlayerState::STOP) { + status.bit_rate = bit_rate; + status.audio_format = audio_format; + status.total_time = total_time; + status.elapsed_time = elapsed_time; + } + + Unlock(); + + return status; +} + +void +player_control::SetError(PlayerError type, Error &&_error) +{ + assert(type != PlayerError::NONE); + assert(_error.IsDefined()); + + error_type = type; + error = std::move(_error); +} + +void +player_control::ClearError() +{ + Lock(); + + if (error_type != PlayerError::NONE) { + error_type = PlayerError::NONE; + error.Clear(); + } + + Unlock(); +} + +char * +player_control::GetErrorMessage() const +{ + Lock(); + char *message = error_type != PlayerError::NONE + ? g_strdup(error.GetMessage()) + : NULL; + Unlock(); + return message; +} + +void +player_control::EnqueueSong(Song *song) +{ + assert(song != NULL); + + Lock(); + EnqueueSongLocked(song); + Unlock(); +} + +bool +player_control::Seek(Song *song, float seek_time) +{ + assert(song != NULL); + + Lock(); + + if (next_song != nullptr) + next_song->Free(); + + next_song = song; + seek_where = seek_time; + SynchronousCommand(PlayerCommand::SEEK); + Unlock(); + + assert(next_song == nullptr); + + idle_add(IDLE_PLAYER); + + return true; +} + +void +player_control::SetCrossFade(float _cross_fade_seconds) +{ + if (_cross_fade_seconds < 0) + _cross_fade_seconds = 0; + cross_fade_seconds = _cross_fade_seconds; + + idle_add(IDLE_OPTIONS); +} + +void +player_control::SetMixRampDb(float _mixramp_db) +{ + mixramp_db = _mixramp_db; + + idle_add(IDLE_OPTIONS); +} + +void +player_control::SetMixRampDelay(float _mixramp_delay_seconds) +{ + mixramp_delay_seconds = _mixramp_delay_seconds; + + idle_add(IDLE_OPTIONS); +} diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx new file mode 100644 index 000000000..4129ef080 --- /dev/null +++ b/src/PlayerControl.hxx @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYER_H +#define MPD_PLAYER_H + +#include "AudioFormat.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <stdint.h> + +struct Song; + +enum class PlayerState : uint8_t { + STOP, + PAUSE, + PLAY +}; + +enum class PlayerCommand : uint8_t { + NONE, + EXIT, + STOP, + PAUSE, + SEEK, + CLOSE_AUDIO, + + /** + * At least one audio_output.enabled flag has been modified; + * commit those changes to the output threads. + */ + UPDATE_AUDIO, + + /** player_control.next_song has been updated */ + QUEUE, + + /** + * cancel pre-decoding player_control.next_song; if the player + * has already started playing this song, it will completely + * stop + */ + CANCEL, + + /** + * Refresh status information in the #player_control struct, + * e.g. elapsed_time. + */ + REFRESH, +}; + +enum class PlayerError : uint8_t { + NONE, + + /** + * The decoder has failed to decode the song. + */ + DECODER, + + /** + * The audio output has failed. + */ + OUTPUT, +}; + +struct player_status { + PlayerState state; + uint16_t bit_rate; + AudioFormat audio_format; + float total_time; + float elapsed_time; +}; + +struct player_control { + unsigned buffer_chunks; + + unsigned int buffered_before_play; + + /** the handle of the player thread, or NULL if the player + thread isn't running */ + GThread *thread; + + /** + * This lock protects #command, #state, #error. + */ + mutable Mutex mutex; + + /** + * Trigger this object after you have modified #command. + */ + Cond cond; + + /** + * This object gets signalled when the player thread has + * finished the #command. It wakes up the client that waits + * (i.e. the main thread). + */ + Cond client_cond; + + PlayerCommand command; + PlayerState state; + + PlayerError error_type; + + /** + * The error that occurred in the player thread. This + * attribute is only valid if #error is not + * #PlayerError::NONE. The object must be freed when this + * object transitions back to #PlayerError::NONE. + */ + Error error; + + uint16_t bit_rate; + AudioFormat audio_format; + float total_time; + float elapsed_time; + + /** + * The next queued song. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + Song *next_song; + + double seek_where; + float cross_fade_seconds; + float mixramp_db; + float mixramp_delay_seconds; + double total_play_time; + + /** + * If this flag is set, then the player will be auto-paused at + * the end of the song, before the next song starts to play. + * + * This is a copy of the queue's "single" flag most of the + * time. + */ + bool border_pause; + + player_control(unsigned buffer_chunks, + unsigned buffered_before_play); + ~player_control(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * Signals the object. The object is temporarily locked by + * this function. + */ + void LockSignal() { + Lock(); + Signal(); + Unlock(); + } + + /** + * Waits for a signal on the object. This function is only + * valid in the player thread. The object must be locked + * prior to calling this function. + */ + void Wait() { + assert(thread == g_thread_self()); + + cond.wait(mutex); + } + + /** + * Wake up the client waiting for command completion. + * + * Caller must lock the object. + */ + void ClientSignal() { + assert(thread == g_thread_self()); + + client_cond.signal(); + } + + /** + * The client calls this method to wait for command + * completion. + * + * Caller must lock the object. + */ + void ClientWait() { + assert(thread != g_thread_self()); + + client_cond.wait(mutex); + } + + /** + * A command has been finished. This method clears the + * command and signals the client. + * + * To be called from the player thread. Caller must lock the + * object. + */ + void CommandFinished() { + assert(command != PlayerCommand::NONE); + + command = PlayerCommand::NONE; + ClientSignal(); + } + +private: + /** + * Wait for the command to be finished by the player thread. + * + * To be called from the main thread. Caller must lock the + * object. + */ + void WaitCommandLocked() { + while (command != PlayerCommand::NONE) + ClientWait(); + } + + /** + * Send a command to the player thread and synchronously wait + * for it to finish. + * + * To be called from the main thread. Caller must lock the + * object. + */ + void SynchronousCommand(PlayerCommand cmd) { + assert(command == PlayerCommand::NONE); + + command = cmd; + Signal(); + WaitCommandLocked(); + } + + /** + * Send a command to the player thread and synchronously wait + * for it to finish. + * + * To be called from the main thread. This method locks the + * object. + */ + void LockSynchronousCommand(PlayerCommand cmd) { + Lock(); + SynchronousCommand(cmd); + Unlock(); + } + +public: + /** + * @param song the song to be queued; the given instance will + * be owned and freed by the player + */ + void Play(Song *song); + + /** + * see PlayerCommand::CANCEL + */ + void Cancel(); + + void SetPause(bool pause_flag); + +private: + void PauseLocked(); + +public: + void Pause(); + + /** + * Set the player's #border_pause flag. + */ + void SetBorderPause(bool border_pause); + + void Kill(); + + gcc_pure + player_status GetStatus(); + + PlayerState GetState() const { + return state; + } + + /** + * Set the error. Discards any previous error condition. + * + * Caller must lock the object. + * + * @param type the error type; must not be #PlayerError::NONE + * @param error detailed error information; must be defined. + */ + void SetError(PlayerError type, Error &&error); + + void ClearError(); + + /** + * 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 *GetErrorMessage() const; + + PlayerError GetErrorType() const { + return error_type; + } + + void Stop(); + + void UpdateAudio(); + +private: + void EnqueueSongLocked(Song *song) { + assert(song != nullptr); + assert(next_song == nullptr); + + next_song = song; + SynchronousCommand(PlayerCommand::QUEUE); + } + +public: + /** + * @param song the song to be queued; the given instance will be owned + * and freed by the player + */ + void EnqueueSong(Song *song); + + /** + * Makes the player thread seek the specified song to a position. + * + * @param song the song to be queued; the given instance will be owned + * and freed by the player + * @return true on success, false on failure (e.g. if MPD isn't + * playing currently) + */ + bool Seek(Song *song, float seek_time); + + void SetCrossFade(float cross_fade_seconds); + + float GetCrossFade() const { + return cross_fade_seconds; + } + + void SetMixRampDb(float mixramp_db); + + float GetMixRampDb() const { + return mixramp_db; + } + + void SetMixRampDelay(float mixramp_delay_seconds); + + float GetMixRampDelay() const { + return mixramp_delay_seconds; + } + + double GetTotalPlayTime() const { + return total_play_time; + } +}; + +#endif diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx new file mode 100644 index 000000000..9ad37eaee --- /dev/null +++ b/src/PlayerThread.cxx @@ -0,0 +1,1203 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlayerThread.hxx" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "Song.hxx" +#include "Main.hxx" +#include "system/FatalError.hxx" +#include "CrossFade.hxx" +#include "PlayerControl.hxx" +#include "OutputAll.hxx" +#include "tag/Tag.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <cmath> + +#include <glib.h> + +#include <string.h> + +static constexpr Domain player_domain("player"); + +enum class CrossFadeState : int8_t { + DISABLED = -1, + UNKNOWN = 0, + ENABLED = 1 +}; + +class Player { + player_control &pc; + + decoder_control &dc; + + MusicBuffer &buffer; + + MusicPipe *pipe; + + /** + * are we waiting for buffered_before_play? + */ + bool buffering; + + /** + * true if the decoder is starting and did not provide data + * yet + */ + bool decoder_starting; + + /** + * is the player paused? + */ + bool paused; + + /** + * is there a new song in pc.next_song? + */ + bool queued; + + /** + * Was any audio output opened successfully? It might have + * failed meanwhile, but was not explicitly closed by the + * player thread. When this flag is unset, some output + * methods must not be called. + */ + bool output_open; + + /** + * the song currently being played + */ + Song *song; + + /** + * is cross fading enabled? + */ + CrossFadeState xfade_state; + + /** + * has cross-fading begun? + */ + bool cross_fading; + + /** + * The number of chunks used for crossfading. + */ + unsigned cross_fade_chunks; + + /** + * The tag of the "next" song during cross-fade. It is + * postponed, and sent to the output thread when the new song + * really begins. + */ + Tag *cross_fade_tag; + + /** + * The current audio format for the audio outputs. + */ + AudioFormat play_audio_format; + + /** + * 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 + * precisely. + */ + float elapsed_time; + +public: + Player(player_control &_pc, decoder_control &_dc, + MusicBuffer &_buffer) + :pc(_pc), dc(_dc), buffer(_buffer), + buffering(false), + decoder_starting(false), + paused(false), + queued(true), + output_open(false), + song(nullptr), + xfade_state(CrossFadeState::UNKNOWN), + cross_fading(false), + cross_fade_chunks(0), + cross_fade_tag(nullptr), + elapsed_time(0.0) {} + +private: + void ClearAndDeletePipe() { + pipe->Clear(buffer); + delete pipe; + } + + void ClearAndReplacePipe(MusicPipe *_pipe) { + ClearAndDeletePipe(); + pipe = _pipe; + } + + void ReplacePipe(MusicPipe *_pipe) { + delete pipe; + pipe = _pipe; + } + + /** + * Start the decoder. + * + * Player lock is not held. + */ + void StartDecoder(MusicPipe &pipe); + + /** + * The decoder has acknowledged the "START" command (see + * player::WaitForDecoder()). This function checks if the decoder + * initialization has completed yet. + * + * The player lock is not held. + */ + bool CheckDecoderStartup(); + + /** + * Stop the decoder and clears (and frees) its music pipe. + * + * Player lock is not held. + */ + void StopDecoder(); + + /** + * Is the decoder still busy on the same song as the player? + * + * Note: this function does not check if the decoder is already + * finished. + */ + gcc_pure + bool IsDecoderAtCurrentSong() const { + assert(pipe != nullptr); + + return dc.pipe == pipe; + } + + /** + * Returns true if the decoder is decoding the next song (or has begun + * decoding it, or has finished doing it), and the player hasn't + * switched to that song yet. + */ + gcc_pure + bool IsDecoderAtNextSong() const { + return dc.pipe != nullptr && !IsDecoderAtCurrentSong(); + } + + /** + * This is the handler for the #PlayerCommand::SEEK command. + * + * The player lock is not held. + */ + bool SeekDecoder(); + + /** + * 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. + */ + bool WaitForDecoder(); + + /** + * Wrapper for audio_output_all_open(). Upon failure, it pauses the + * player. + * + * @return true on success + */ + bool OpenOutput(); + + /** + * Obtains the next chunk from the music pipe, optionally applies + * cross-fading, and sends it to all audio outputs. + * + * @return true on success, false on error (playback will be stopped) + */ + bool PlayNextChunk(); + + /** + * 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. + */ + bool SendSilence(); + + /** + * Player lock must be held before calling. + */ + void ProcessCommand(); + + /** + * This is called at the border between two songs: the audio output + * 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) + */ + bool SongBorder(); + +public: + /* + * The main loop of the player thread, during playback. This + * is basically a state machine, which multiplexes data + * between the decoder thread and the output threads. + */ + void Run(); +}; + +static void +player_command_finished(player_control &pc) +{ + pc.Lock(); + pc.CommandFinished(); + pc.Unlock(); +} + +void +Player::StartDecoder(MusicPipe &_pipe) +{ + assert(queued || pc.command == PlayerCommand::SEEK); + assert(pc.next_song != nullptr); + + unsigned start_ms = pc.next_song->start_ms; + if (pc.command == PlayerCommand::SEEK) + start_ms += (unsigned)(pc.seek_where * 1000); + + dc.Start(pc.next_song->DupDetached(), + start_ms, pc.next_song->end_ms, + buffer, _pipe); +} + +void +Player::StopDecoder() +{ + dc.Stop(); + + if (dc.pipe != nullptr) { + /* clear and free the decoder pipe */ + + dc.pipe->Clear(buffer); + + if (dc.pipe != pipe) + delete dc.pipe; + + dc.pipe = nullptr; + } +} + +bool +Player::WaitForDecoder() +{ + assert(queued || pc.command == PlayerCommand::SEEK); + assert(pc.next_song != nullptr); + + queued = false; + + Error error = dc.LockGetError(); + if (error.IsDefined()) { + pc.Lock(); + pc.SetError(PlayerError::DECODER, std::move(error)); + + pc.next_song->Free(); + pc.next_song = nullptr; + + pc.Unlock(); + + return false; + } + + if (song != nullptr) + song->Free(); + + song = pc.next_song; + elapsed_time = 0.0; + + /* set the "starting" flag, which will be cleared by + player_check_decoder_startup() */ + decoder_starting = true; + + pc.Lock(); + + /* update player_control's song information */ + pc.total_time = pc.next_song->GetDuration(); + pc.bit_rate = 0; + pc.audio_format.Clear(); + + /* clear the queued song */ + pc.next_song = nullptr; + + pc.Unlock(); + + /* call syncPlaylistWithQueue() in the main thread */ + GlobalEvents::Emit(GlobalEvents::PLAYLIST); + + return true; +} + +/** + * Returns the real duration of the song, comprising the duration + * indicated by the decoder plugin. + */ +static double +real_song_duration(const Song *song, double decoder_duration) +{ + assert(song != nullptr); + + if (decoder_duration <= 0.0) + /* the decoder plugin didn't provide information; fall + back to Song::GetDuration() */ + return song->GetDuration(); + + 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; +} + +bool +Player::OpenOutput() +{ + assert(play_audio_format.IsDefined()); + assert(pc.state == PlayerState::PLAY || + pc.state == PlayerState::PAUSE); + + Error error; + if (audio_output_all_open(play_audio_format, buffer, error)) { + output_open = true; + paused = false; + + pc.Lock(); + pc.state = PlayerState::PLAY; + pc.Unlock(); + + idle_add(IDLE_PLAYER); + + return true; + } else { + LogError(error); + + output_open = false; + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + paused = true; + + pc.Lock(); + pc.SetError(PlayerError::OUTPUT, std::move(error)); + pc.state = PlayerState::PAUSE; + pc.Unlock(); + + idle_add(IDLE_PLAYER); + + return false; + } +} + +bool +Player::CheckDecoderStartup() +{ + assert(decoder_starting); + + dc.Lock(); + + Error error = dc.GetError(); + if (error.IsDefined()) { + /* the decoder failed */ + dc.Unlock(); + + pc.Lock(); + pc.SetError(PlayerError::DECODER, std::move(error)); + pc.Unlock(); + + return false; + } else if (!dc.IsStarting()) { + /* the decoder is ready and ok */ + + dc.Unlock(); + + if (output_open && + !audio_output_all_wait(&pc, 1)) + /* the output devices havn't finished playing + all chunks yet - wait for that */ + return true; + + pc.Lock(); + pc.total_time = real_song_duration(dc.song, dc.total_time); + pc.audio_format = dc.in_audio_format; + pc.Unlock(); + + idle_add(IDLE_PLAYER); + + play_audio_format = dc.out_audio_format; + decoder_starting = false; + + if (!paused && !OpenOutput()) { + char *uri = dc.song->GetURI(); + FormatError(player_domain, + "problems opening audio device " + "while playing \"%s\"", uri); + g_free(uri); + + return true; + } + + return true; + } else { + /* the decoder is not yet ready; wait + some more */ + dc.WaitForDecoder(); + dc.Unlock(); + + return true; + } +} + +bool +Player::SendSilence() +{ + assert(output_open); + assert(play_audio_format.IsDefined()); + + struct music_chunk *chunk = buffer.Allocate(); + if (chunk == nullptr) { + LogError(player_domain, "Failed to allocate silence buffer"); + return false; + } + +#ifndef NDEBUG + chunk->audio_format = play_audio_format; +#endif + + const size_t frame_size = play_audio_format.GetFrameSize(); + /* 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); + + Error error; + if (!audio_output_all_play(chunk, error)) { + LogError(error); + buffer.Return(chunk); + return false; + } + + return true; +} + +inline bool +Player::SeekDecoder() +{ + assert(pc.next_song != nullptr); + + const unsigned start_ms = pc.next_song->start_ms; + + if (!dc.LockIsCurrentSong(pc.next_song)) { + /* the decoder is already decoding the "next" song - + stop it and start the previous song again */ + + StopDecoder(); + + /* clear music chunks which might still reside in the + pipe */ + pipe->Clear(buffer); + + /* re-start the decoder */ + StartDecoder(*pipe); + if (!WaitForDecoder()) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + } else { + if (!IsDecoderAtCurrentSong()) { + /* the decoder is already decoding the "next" song, + but it is the same song file; exchange the pipe */ + ClearAndReplacePipe(dc.pipe); + } + + pc.next_song->Free(); + pc.next_song = nullptr; + queued = false; + } + + /* wait for the decoder to complete initialization */ + + while (decoder_starting) { + if (!CheckDecoderStartup()) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + } + + /* send the SEEK command */ + + double where = pc.seek_where; + if (where > pc.total_time) + where = pc.total_time - 0.1; + if (where < 0.0) + where = 0.0; + + if (!dc.Seek(where + start_ms / 1000.0)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + + elapsed_time = where; + + player_command_finished(pc); + + xfade_state = CrossFadeState::UNKNOWN; + + /* re-fill the buffer after seeking */ + buffering = true; + + audio_output_all_cancel(); + + return true; +} + +inline void +Player::ProcessCommand() +{ + switch (pc.command) { + case PlayerCommand::NONE: + case PlayerCommand::STOP: + case PlayerCommand::EXIT: + case PlayerCommand::CLOSE_AUDIO: + break; + + case PlayerCommand::UPDATE_AUDIO: + pc.Unlock(); + audio_output_all_enable_disable(); + pc.Lock(); + pc.CommandFinished(); + break; + + case PlayerCommand::QUEUE: + assert(pc.next_song != nullptr); + assert(!queued); + assert(!IsDecoderAtNextSong()); + + queued = true; + pc.CommandFinished(); + break; + + case PlayerCommand::PAUSE: + pc.Unlock(); + + paused = !paused; + if (paused) { + audio_output_all_pause(); + pc.Lock(); + + pc.state = PlayerState::PAUSE; + } else if (!play_audio_format.IsDefined()) { + /* the decoder hasn't provided an audio format + yet - don't open the audio device yet */ + pc.Lock(); + + pc.state = PlayerState::PLAY; + } else { + OpenOutput(); + + pc.Lock(); + } + + pc.CommandFinished(); + break; + + case PlayerCommand::SEEK: + pc.Unlock(); + SeekDecoder(); + pc.Lock(); + break; + + case PlayerCommand::CANCEL: + if (pc.next_song == nullptr) { + /* the cancel request arrived too late, we're + already playing the queued song... stop + everything now */ + pc.command = PlayerCommand::STOP; + return; + } + + if (IsDecoderAtNextSong()) { + /* the decoder is already decoding the song - + stop it and reset the position */ + pc.Unlock(); + StopDecoder(); + pc.Lock(); + } + + pc.next_song->Free(); + pc.next_song = nullptr; + queued = false; + pc.CommandFinished(); + break; + + case PlayerCommand::REFRESH: + if (output_open && !paused) { + pc.Unlock(); + audio_output_all_check(); + pc.Lock(); + } + + pc.elapsed_time = audio_output_all_get_elapsed_time(); + if (pc.elapsed_time < 0.0) + pc.elapsed_time = elapsed_time; + + pc.CommandFinished(); + break; + } +} + +static void +update_song_tag(Song *song, const Tag &new_tag) +{ + if (song->IsFile()) + /* don't update tags of local files, only remote + streams may change tags dynamically */ + return; + + Tag *old_tag = song->tag; + song->tag = new Tag(new_tag); + + delete old_tag; + + /* the main thread will update the playlist version when he + receives this event */ + GlobalEvents::Emit(GlobalEvents::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(player_control &pc, + Song *song, struct music_chunk *chunk, + MusicBuffer &buffer, + const AudioFormat format, + Error &error) +{ + assert(chunk->CheckFormat(format)); + + if (chunk->tag != nullptr) + update_song_tag(song, *chunk->tag); + + if (chunk->length == 0) { + buffer.Return(chunk); + return true; + } + + pc.Lock(); + pc.bit_rate = chunk->bit_rate; + pc.Unlock(); + + /* send the chunk to the audio outputs */ + + if (!audio_output_all_play(chunk, error)) + return false; + + pc.total_play_time += (double)chunk->length / + format.GetTimeToSize(); + return true; +} + +inline bool +Player::PlayNextChunk() +{ + if (!audio_output_all_wait(&pc, 64)) + /* the output pipe is still large enough, don't send + another chunk */ + return true; + + unsigned cross_fade_position; + struct music_chunk *chunk = nullptr; + if (xfade_state == CrossFadeState::ENABLED && IsDecoderAtNextSong() && + (cross_fade_position = pipe->GetSize()) <= cross_fade_chunks) { + /* perform cross fade */ + music_chunk *other_chunk = dc.pipe->Shift(); + + if (!cross_fading) { + /* beginning of the cross fade - adjust + crossFadeChunks which might be bigger than + the remaining number of chunks in the old + song */ + cross_fade_chunks = cross_fade_position; + cross_fading = true; + } + + if (other_chunk != nullptr) { + chunk = pipe->Shift(); + assert(chunk != nullptr); + assert(chunk->other == nullptr); + + /* don't send the tags of the new song (which + is being faded in) yet; postpone it until + the current song is faded out */ + cross_fade_tag = + Tag::MergeReplace(cross_fade_tag, + other_chunk->tag); + other_chunk->tag = nullptr; + + if (std::isnan(pc.mixramp_delay_seconds)) { + chunk->mix_ratio = ((float)cross_fade_position) + / cross_fade_chunks; + } else { + chunk->mix_ratio = nan(""); + } + + if (other_chunk->IsEmpty()) { + /* 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 */ + buffer.Return(other_chunk); + other_chunk = nullptr; + } + + chunk->other = other_chunk; + } else { + /* there are not enough decoded chunks yet */ + + dc.Lock(); + + if (dc.IsIdle()) { + /* the decoder isn't running, abort + cross fading */ + dc.Unlock(); + + xfade_state = CrossFadeState::DISABLED; + } else { + /* wait for the decoder */ + dc.Signal(); + dc.WaitForDecoder(); + dc.Unlock(); + + return true; + } + } + } + + if (chunk == nullptr) + chunk = pipe->Shift(); + + assert(chunk != nullptr); + + /* insert the postponed tag if cross-fading is finished */ + + if (xfade_state != CrossFadeState::ENABLED && cross_fade_tag != nullptr) { + chunk->tag = Tag::MergeReplace(chunk->tag, cross_fade_tag); + cross_fade_tag = nullptr; + } + + /* play the current chunk */ + + Error error; + if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) { + LogError(error); + + buffer.Return(chunk); + + pc.Lock(); + + pc.SetError(PlayerError::OUTPUT, std::move(error)); + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + pc.state = PlayerState::PAUSE; + paused = true; + + pc.Unlock(); + + idle_add(IDLE_PLAYER); + + 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 */ + dc.Lock(); + if (!dc.IsIdle() && + dc.pipe->GetSize() <= (pc.buffered_before_play + + buffer.GetSize() * 3) / 4) + dc.Signal(); + dc.Unlock(); + + return true; +} + +inline bool +Player::SongBorder() +{ + xfade_state = CrossFadeState::UNKNOWN; + + char *uri = song->GetURI(); + FormatInfo(player_domain, "played \"%s\"", uri); + g_free(uri); + + ReplacePipe(dc.pipe); + + audio_output_all_song_border(); + + if (!WaitForDecoder()) + return false; + + pc.Lock(); + + const bool border_pause = pc.border_pause; + if (border_pause) { + paused = true; + pc.state = PlayerState::PAUSE; + } + + pc.Unlock(); + + if (border_pause) + idle_add(IDLE_PLAYER); + + return true; +} + +inline void +Player::Run() +{ + pipe = new MusicPipe(); + + StartDecoder(*pipe); + if (!WaitForDecoder()) { + assert(song == nullptr); + + StopDecoder(); + player_command_finished(pc); + delete pipe; + return; + } + + pc.Lock(); + pc.state = PlayerState::PLAY; + + if (pc.command == PlayerCommand::SEEK) + elapsed_time = pc.seek_where; + + pc.CommandFinished(); + + while (true) { + ProcessCommand(); + if (pc.command == PlayerCommand::STOP || + pc.command == PlayerCommand::EXIT || + pc.command == PlayerCommand::CLOSE_AUDIO) { + pc.Unlock(); + audio_output_all_cancel(); + break; + } + + pc.Unlock(); + + if (buffering) { + /* buffering at the start of the song - wait + until the buffer is large enough, to + prevent stuttering on slow machines */ + + if (pipe->GetSize() < pc.buffered_before_play && + !dc.LockIsIdle()) { + /* not enough decoded buffer space yet */ + + if (!paused && output_open && + audio_output_all_check() < 4 && + !SendSilence()) + break; + + dc.Lock(); + /* XXX race condition: check decoder again */ + dc.WaitForDecoder(); + dc.Unlock(); + pc.Lock(); + continue; + } else { + /* buffering is complete */ + buffering = false; + } + } + + if (decoder_starting) { + /* wait until the decoder is initialized completely */ + + if (!CheckDecoderStartup()) + break; + + pc.Lock(); + continue; + } + +#ifndef NDEBUG + /* + music_pipe_check_format(&play_audio_format, + next_song_chunk, + &dc.out_audio_format); + */ +#endif + + if (dc.LockIsIdle() && queued && dc.pipe == pipe) { + /* the decoder has finished the current song; + make it decode the next song */ + + assert(dc.pipe == nullptr || dc.pipe == pipe); + + StartDecoder(*new MusicPipe()); + } + + if (/* no cross-fading if MPD is going to pause at the + end of the current song */ + !pc.border_pause && + IsDecoderAtNextSong() && + xfade_state == CrossFadeState::UNKNOWN && + !dc.LockIsStarting()) { + /* enable cross fading in this song? if yes, + calculate how many chunks will be required + for it */ + cross_fade_chunks = + 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, + play_audio_format, + buffer.GetSize() - + pc.buffered_before_play); + if (cross_fade_chunks > 0) { + xfade_state = CrossFadeState::ENABLED; + cross_fading = false; + } else + /* cross fading is disabled or the + next song is too short */ + xfade_state = CrossFadeState::DISABLED; + } + + if (paused) { + pc.Lock(); + + if (pc.command == PlayerCommand::NONE) + pc.Wait(); + continue; + } else if (!pipe->IsEmpty()) { + /* at least one music chunk is ready - send it + to the audio output */ + + PlayNextChunk(); + } else if (audio_output_all_check() > 0) { + /* not enough data from decoder, but the + output thread is still busy, so it's + okay */ + + /* XXX synchronize in a better way */ + g_usleep(10000); + } else if (IsDecoderAtNextSong()) { + /* at the beginning of a new song */ + + if (!SongBorder()) + break; + } else if (dc.LockIsIdle()) { + /* check the size of the pipe again, because + the decoder thread may have added something + since we last checked */ + if (pipe->IsEmpty()) { + /* wait for the hardware to finish + playback */ + audio_output_all_drain(); + break; + } + } else if (output_open) { + /* the decoder is too busy and hasn't provided + new PCM data in time: send silence (if the + output pipe is empty) */ + if (!SendSilence()) + break; + } + + pc.Lock(); + } + + StopDecoder(); + + ClearAndDeletePipe(); + + delete cross_fade_tag; + + if (song != nullptr) + song->Free(); + + pc.Lock(); + + if (queued) { + assert(pc.next_song != nullptr); + pc.next_song->Free(); + pc.next_song = nullptr; + } + + pc.state = PlayerState::STOP; + + pc.Unlock(); +} + +static void +do_play(player_control &pc, decoder_control &dc, + MusicBuffer &buffer) +{ + Player player(pc, dc, buffer); + player.Run(); +} + +static gpointer +player_task(gpointer arg) +{ + player_control &pc = *(player_control *)arg; + + decoder_control dc; + decoder_thread_start(&dc); + + MusicBuffer buffer(pc.buffer_chunks); + + pc.Lock(); + + while (1) { + switch (pc.command) { + case PlayerCommand::SEEK: + case PlayerCommand::QUEUE: + assert(pc.next_song != nullptr); + + pc.Unlock(); + do_play(pc, dc, buffer); + GlobalEvents::Emit(GlobalEvents::PLAYLIST); + pc.Lock(); + break; + + case PlayerCommand::STOP: + pc.Unlock(); + audio_output_all_cancel(); + pc.Lock(); + + /* fall through */ + + case PlayerCommand::PAUSE: + if (pc.next_song != nullptr) { + pc.next_song->Free(); + pc.next_song = nullptr; + } + + pc.CommandFinished(); + break; + + case PlayerCommand::CLOSE_AUDIO: + pc.Unlock(); + + audio_output_all_release(); + + pc.Lock(); + pc.CommandFinished(); + + assert(buffer.IsEmptyUnsafe()); + + break; + + case PlayerCommand::UPDATE_AUDIO: + pc.Unlock(); + audio_output_all_enable_disable(); + pc.Lock(); + pc.CommandFinished(); + break; + + case PlayerCommand::EXIT: + pc.Unlock(); + + dc.Quit(); + + audio_output_all_close(); + + player_command_finished(pc); + return nullptr; + + case PlayerCommand::CANCEL: + if (pc.next_song != nullptr) { + pc.next_song->Free(); + pc.next_song = nullptr; + } + + pc.CommandFinished(); + break; + + case PlayerCommand::REFRESH: + /* no-op when not playing */ + pc.CommandFinished(); + break; + + case PlayerCommand::NONE: + pc.Wait(); + break; + } + } +} + +void +player_create(player_control &pc) +{ + assert(pc.thread == nullptr); + +#if GLIB_CHECK_VERSION(2,32,0) + pc.thread = g_thread_new("player", player_task, &pc); +#else + GError *e = nullptr; + pc.thread = g_thread_create(player_task, &pc, true, &e); + if (pc.thread == nullptr) + FatalError("Failed to spawn player task", e); +#endif +} diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx new file mode 100644 index 000000000..97e773606 --- /dev/null +++ b/src/PlayerThread.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * The player thread controls the playback. It acts as a bridge + * between the decoder thread and the output thread(s): it receives + * #music_chunk objects from the decoder, optionally mixes them + * (cross-fading), applies software volume, and sends them to the + * audio outputs via audio_output_all_play(). + * + * It is controlled by the main thread (the playlist code), see + * player_control.h. The playlist enqueues new songs into the player + * thread and sends it commands. + * + * The player thread itself does not do any I/O. It synchronizes with + * other threads via #GMutex and #GCond objects, and passes + * #music_chunk instances around in #MusicPipe objects. + */ + +#ifndef MPD_PLAYER_THREAD_HXX +#define MPD_PLAYER_THREAD_HXX + +struct player_control; + +void +player_create(player_control &pc); + +#endif diff --git a/src/Playlist.cxx b/src/Playlist.cxx new file mode 100644 index 000000000..9f3758c52 --- /dev/null +++ b/src/Playlist.cxx @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "Song.hxx" +#include "Idle.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> + +void +playlist::FullIncrementVersions() +{ + queue.ModifyAll(); + idle_add(IDLE_PLAYLIST); +} + +void +playlist::TagChanged() +{ + if (!playing) + return; + + assert(current >= 0); + + queue.ModifyAtOrder(current); + idle_add(IDLE_PLAYLIST); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, + unsigned order) +{ + char *uri; + + assert(playlist->queue.IsValidOrder(order)); + + playlist->queued = order; + + Song *song = playlist->queue.GetOrder(order)->DupDetached(); + + uri = song->GetURI(); + FormatDebug(playlist_domain, "queue song %i:\"%s\"", + playlist->queued, uri); + g_free(uri); + + pc->EnqueueSong(song); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(struct playlist *playlist, struct player_control *pc) +{ + assert(pc->next_song == nullptr); + assert(playlist->queued >= -1); + + /* queued song has started: copy queued to current, + and notify the clients */ + + int current = playlist->current; + playlist->current = playlist->queued; + playlist->queued = -1; + + if(playlist->queue.consume) + playlist->DeleteOrder(*pc, current); + + idle_add(IDLE_PLAYER); +} + +const Song * +playlist::GetQueuedSong() const +{ + return playing && queued >= 0 + ? queue.GetOrder(queued) + : nullptr; +} + +void +playlist::UpdateQueuedSong(player_control &pc, const Song *prev) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert((queued < 0) == (prev == nullptr)); + + const int next_order = current >= 0 + ? queue.GetNextOrder(current) + : 0; + + if (next_order == 0 && queue.random && !queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + const unsigned current_position = + queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that the current still points to + the current song, after the song order has been + shuffled */ + current = queue.PositionToOrder(current_position); + } + + const Song *const next_song = next_order >= 0 + ? queue.GetOrder(next_order) + : nullptr; + + if (prev != nullptr && next_song != prev) { + /* clear the currently queued song */ + pc.Cancel(); + queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(this, &pc, next_order); + else + queued = next_order; + } +} + +void +playlist::PlayOrder(player_control &pc, int order) +{ + playing = true; + queued = -1; + + Song *song = queue.GetOrder(order)->DupDetached(); + + char *uri = song->GetURI(); + FormatDebug(playlist_domain, "play %i:\"%s\"", order, uri); + g_free(uri); + + pc.Play(song); + current = order; +} + +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc); + +void +playlist::SyncWithPlayer(player_control &pc) +{ + if (!playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + pc.Lock(); + const PlayerState pc_state = pc.GetState(); + const Song *pc_next_song = pc.next_song; + pc.Unlock(); + + if (pc_state == PlayerState::STOP) + /* the player thread has stopped: check if playback + should be restarted with the next song. That can + happen if the playlist isn't filling the queue fast + enough */ + playlist_resume_playback(this, &pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == nullptr && queued != -1) + playlist_song_started(this, &pc); + + pc.Lock(); + pc_next_song = pc.next_song; + pc.Unlock(); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == nullptr && queued < 0) + UpdateQueuedSong(pc, nullptr); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc) +{ + assert(playlist->playing); + assert(pc->GetState() == PlayerState::STOP); + + const auto error = pc->GetErrorType(); + if (error == PlayerError::NONE) + playlist->error_count = 0; + else + ++playlist->error_count; + + if ((playlist->stop_on_error && error != PlayerError::NONE) || + error == PlayerError::OUTPUT || + playlist->error_count >= playlist->queue.GetLength()) + /* too many errors, or critical error: stop + playback */ + playlist->Stop(*pc); + else + /* continue playback at the next song */ + playlist->PlayNext(*pc); +} + +void +playlist::SetRepeat(player_control &pc, bool status) +{ + if (status == queue.repeat) + return; + + queue.repeat = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(struct playlist *playlist) +{ + if (playlist->current >= 0) + /* update playlist.current, order==position now */ + playlist->current = playlist->queue.OrderToPosition(playlist->current); + + playlist->queue.RestoreOrder(); +} + +void +playlist::SetSingle(player_control &pc, bool status) +{ + if (status == queue.single) + return; + + queue.single = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetConsume(bool status) +{ + if (status == queue.consume) + return; + + queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetRandom(player_control &pc, bool status) +{ + if (status == queue.random) + return; + + const Song *const queued_song = GetQueuedSong(); + + queue.random = status; + + if (queue.random) { + /* shuffle the queue order, but preserve current */ + + const int current_position = GetCurrentPosition(); + + queue.ShuffleOrder(); + + if (current_position >= 0) { + /* make sure the current song is the first in + the order list, so the whole rest of the + playlist is played after that */ + unsigned current_order = + queue.PositionToOrder(current_position); + queue.SwapOrders(0, current_order); + current = 0; + } else + current = -1; + } else + playlist_order(this); + + UpdateQueuedSong(pc, queued_song); + + idle_add(IDLE_OPTIONS); +} + +int +playlist::GetCurrentPosition() const +{ + return current >= 0 + ? queue.OrderToPosition(current) + : -1; +} + +int +playlist::GetNextPosition() const +{ + if (current < 0) + return -1; + + if (queue.single && queue.repeat) + return queue.OrderToPosition(current); + else if (queue.IsValidOrder(current + 1)) + return queue.OrderToPosition(current + 1); + else if (queue.repeat) + return queue.OrderToPosition(0); + + return -1; +} diff --git a/src/Playlist.hxx b/src/Playlist.hxx new file mode 100644 index 000000000..82cac37c5 --- /dev/null +++ b/src/Playlist.hxx @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_HXX + +#include "Queue.hxx" +#include "PlaylistError.hxx" + +struct player_control; +struct Song; + +struct playlist { + /** + * The song queue - it contains the "real" playlist. + */ + struct queue queue; + + /** + * This value is true if the player is currently playing (or + * should be playing). + */ + bool playing; + + /** + * If true, then any error is fatal; if false, MPD will + * attempt to play the next song on non-fatal errors. During + * seeking, this flag is set. + */ + bool stop_on_error; + + /** + * Number of errors since playback was started. If this + * number exceeds the length of the playlist, MPD gives up, + * because all songs have been tried. + */ + unsigned error_count; + + /** + * The "current song pointer". This is the song which is + * played when we get the "play" command. It is also the song + * which is currently being played. + */ + int current; + + /** + * The "next" song to be played, when the current one + * finishes. The decoder thread may start decoding and + * buffering it, while the "current" song is still playing. + * + * This variable is only valid if #playing is true. + */ + int queued; + + playlist(unsigned max_length) + :queue(max_length), playing(false), current(-1), queued(-1) { + } + + ~playlist() { + } + + uint32_t GetVersion() const { + return queue.version; + } + + unsigned GetLength() const { + return queue.GetLength(); + } + + unsigned PositionToId(unsigned position) const { + return queue.PositionToId(position); + } + + gcc_pure + int GetCurrentPosition() const; + + gcc_pure + int GetNextPosition() const; + + /** + * Returns the song object which is currently queued. Returns + * none if there is none (yet?) or if MPD isn't playing. + */ + gcc_pure + const Song *GetQueuedSong() const; + + /** + * 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 SyncWithPlayer(player_control &pc); + +protected: + /** + * Called by all editing methods after a modification. + * Updates the queue version and emits #IDLE_PLAYLIST. + */ + void OnModified(); + + /** + * Updates the "queued song". Calculates the next song + * according to the current one (if MPD isn't playing, it + * takes the first song), and queues this song. Clears the + * old queued song if there was one. + * + * @param prev the song which was previously queued, as + * determined by playlist_get_queued_song() + */ + void UpdateQueuedSong(player_control &pc, const Song *prev); + +public: + void Clear(player_control &pc); + + void TagChanged(); + + void FullIncrementVersions(); + + enum playlist_result AppendSong(player_control &pc, + Song *song, + unsigned *added_id=nullptr); + + /** + * Appends a local file (outside the music database) to the + * playlist. + * + * Note: the caller is responsible for checking permissions. + */ + enum playlist_result AppendFile(player_control &pc, + const char *path_utf8, + unsigned *added_id=nullptr); + + enum playlist_result AppendURI(player_control &pc, + const char *uri_utf8, + unsigned *added_id=nullptr); + +protected: + void DeleteInternal(player_control &pc, + unsigned song, const Song **queued_p); + +public: + enum playlist_result DeletePosition(player_control &pc, + unsigned position); + + enum playlist_result DeleteOrder(player_control &pc, + unsigned order) { + return DeletePosition(pc, queue.OrderToPosition(order)); + } + + enum playlist_result DeleteId(player_control &pc, unsigned id); + + /** + * 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 DeleteRange(player_control &pc, + unsigned start, unsigned end); + + void DeleteSong(player_control &pc, const Song &song); + + void Shuffle(player_control &pc, unsigned start, unsigned end); + + enum playlist_result MoveRange(player_control &pc, + unsigned start, unsigned end, int to); + + enum playlist_result MoveId(player_control &pc, unsigned id, int to); + + enum playlist_result SwapPositions(player_control &pc, + unsigned song1, unsigned song2); + + enum playlist_result SwapIds(player_control &pc, + unsigned id1, unsigned id2); + + enum playlist_result SetPriorityRange(player_control &pc, + unsigned start_position, + unsigned end_position, + uint8_t priority); + + enum playlist_result SetPriorityId(player_control &pc, + unsigned song_id, uint8_t priority); + + void Stop(player_control &pc); + + enum playlist_result PlayPosition(player_control &pc, int position); + + void PlayOrder(player_control &pc, int order); + + enum playlist_result PlayId(player_control &pc, int id); + + void PlayNext(player_control &pc); + + void PlayPrevious(player_control &pc); + + enum playlist_result SeekSongPosition(player_control &pc, + unsigned song_position, + float seek_time); + + enum playlist_result SeekSongId(player_control &pc, + unsigned song_id, float seek_time); + + /** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ + enum playlist_result SeekCurrent(player_control &pc, + float seek_time, bool relative); + + bool GetRepeat() const { + return queue.repeat; + } + + void SetRepeat(player_control &pc, bool new_value); + + bool GetRandom() const { + return queue.random; + } + + void SetRandom(player_control &pc, bool new_value); + + bool GetSingle() const { + return queue.single; + } + + void SetSingle(player_control &pc, bool new_value); + + bool GetConsume() const { + return queue.consume; + } + + void SetConsume(bool new_value); +}; + +#endif diff --git a/src/PlaylistAny.cxx b/src/PlaylistAny.cxx new file mode 100644 index 000000000..997c59a4e --- /dev/null +++ b/src/PlaylistAny.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistAny.hxx" +#include "PlaylistMapper.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistError.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "InputStream.hxx" +#include "Log.hxx" + +#include <assert.h> + +static SongEnumerator * +playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + assert(uri_has_scheme(uri)); + + SongEnumerator *playlist = playlist_list_open_uri(uri, mutex, cond); + if (playlist != nullptr) { + *is_r = nullptr; + return playlist; + } + + Error error; + input_stream *is = input_stream::Open(uri, mutex, cond, error); + if (is == nullptr) { + if (error.IsDefined()) + FormatError(error, "Failed to open %s", uri); + + return nullptr; + } + + playlist = playlist_list_open_stream(is, uri); + if (playlist == nullptr) { + is->Close(); + return nullptr; + } + + *is_r = is; + return playlist; +} + +SongEnumerator * +playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + return uri_has_scheme(uri) + ? playlist_open_remote(uri, mutex, cond, is_r) + : playlist_mapper_open(uri, mutex, cond, is_r); +} diff --git a/src/PlaylistAny.hxx b/src/PlaylistAny.hxx new file mode 100644 index 000000000..951fa1099 --- /dev/null +++ b/src/PlaylistAny.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_ANY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +class SongEnumerator; +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 + */ +SongEnumerator * +playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r); + +#endif diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx new file mode 100644 index 000000000..785267c1d --- /dev/null +++ b/src/PlaylistCommands.cxx @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistCommands.hxx" +#include "DatabasePlaylist.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistVector.hxx" +#include "PlaylistQueue.hxx" +#include "TimePrint.hxx" +#include "ClientInternal.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Playlist.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stdlib.h> + +static void +print_spl_list(Client *client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +enum command_return +handle_save(Client *client, gcc_unused int argc, char *argv[]) +{ + enum playlist_result result; + + result = spl_save_playlist(argv[1], &client->playlist); + return print_playlist_result(client, result); +} + +enum command_return +handle_load(Client *client, int argc, char *argv[]) +{ + unsigned start_index, end_index; + + if (argc < 3) { + start_index = 0; + end_index = unsigned(-1); + } else if (!check_range(client, &start_index, &end_index, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result; + + result = playlist_open_into_queue(argv[1], + start_index, end_index, + &client->playlist, + client->player_control, true); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return print_playlist_result(client, result); + + Error error; + if (playlist_load_spl(&client->playlist, client->player_control, + argv[1], start_index, end_index, + error)) + return COMMAND_RETURN_OK; + + if (error.IsDomain(playlist_domain) && + error.GetCode() == PLAYLIST_RESULT_BAD_NAME) { + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the Error object to show "no such + playlist" instead */ + Error error2(playlist_domain, PLAYLIST_RESULT_NO_SUCH_LIST, + error.GetMessage()); + error = std::move(error2); + } + + return print_error(client, error); +} + +enum command_return +handle_listplaylist(Client *client, gcc_unused int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], false)) + return COMMAND_RETURN_OK; + + Error error; + return spl_print(client, argv[1], false, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_listplaylistinfo(Client *client, + gcc_unused int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], true)) + return COMMAND_RETURN_OK; + + Error error; + return spl_print(client, argv[1], true, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rm(Client *client, gcc_unused int argc, char *argv[]) +{ + Error error; + return spl_delete(argv[1], error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_rename(Client *client, gcc_unused int argc, char *argv[]) +{ + Error error; + return spl_rename(argv[1], argv[2], error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistdelete(Client *client, + gcc_unused int argc, char *argv[]) { + char *playlist = argv[1]; + unsigned from; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + + Error error; + return spl_remove_index(playlist, from, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistmove(Client *client, gcc_unused int argc, char *argv[]) +{ + char *playlist = argv[1]; + unsigned from, to; + + if (!check_unsigned(client, &from, argv[2])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &to, argv[3])) + return COMMAND_RETURN_ERROR; + + Error error; + return spl_move_index(playlist, from, to, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistclear(Client *client, gcc_unused int argc, char *argv[]) +{ + Error error; + return spl_clear(argv[1], error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_playlistadd(Client *client, gcc_unused int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *uri = argv[2]; + + bool success; + Error error; + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + success = spl_append_uri(uri, playlist, error); + } else + success = search_add_to_playlist(uri, playlist, nullptr, + error); + + if (!success && !error.IsDefined()) { + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return COMMAND_RETURN_ERROR; + } + + return success ? COMMAND_RETURN_OK : print_error(client, error); +} + +enum command_return +handle_listplaylists(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + Error error; + const auto list = ListPlaylistFiles(error); + if (list.empty() && error.IsDefined()) + return print_error(client, error); + + print_spl_list(client, list); + return COMMAND_RETURN_OK; +} diff --git a/src/PlaylistCommands.hxx b/src/PlaylistCommands.hxx new file mode 100644 index 000000000..067f428b6 --- /dev/null +++ b/src/PlaylistCommands.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_COMMANDS_HXX +#define MPD_PLAYLIST_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_save(Client *client, int argc, char *argv[]); + +enum command_return +handle_load(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylist(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_rm(Client *client, int argc, char *argv[]); + +enum command_return +handle_rename(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistdelete(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistmove(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistclear(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistadd(Client *client, int argc, char *argv[]); + +enum command_return +handle_listplaylists(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx new file mode 100644 index 000000000..c6b5690bb --- /dev/null +++ b/src/PlaylistControl.cxx @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for controlling playback on the playlist level. + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "Song.hxx" +#include "Log.hxx" + +void +playlist::Stop(player_control &pc) +{ + if (!playing) + return; + + assert(current >= 0); + + FormatDebug(playlist_domain, "stop"); + pc.Stop(); + queued = -1; + playing = false; + + if (queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + current = queue.PositionToOrder(current_position); + } +} + +enum playlist_result +playlist::PlayPosition(player_control &pc, int song) +{ + pc.ClearError(); + + unsigned i = song; + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue.IsEmpty()) + return PLAYLIST_RESULT_SUCCESS; + + if (playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc.SetPause(false); + return PLAYLIST_RESULT_SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = current >= 0 + ? current + : 0; + } else if (!queue.IsValidPosition(song)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (queue.random) { + if (song >= 0) + /* "i" is currently the song position (which + would be equal to the order number in + no-random mode); convert it to a order + number, because random mode is enabled */ + i = queue.PositionToOrder(song); + + if (!playing) + current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue.SwapOrders(i, current); + i = current; + } + + stop_on_error = false; + error_count = 0; + + PlayOrder(pc, i); + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::PlayId(player_control &pc, int id) +{ + if (id == -1) + return PlayPosition(pc, id); + + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return PlayPosition(pc, song); +} + +void +playlist::PlayNext(player_control &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert(queue.IsValidOrder(current)); + + const int old_current = current; + stop_on_error = false; + + /* determine the next song from the queue's order list */ + + const int next_order = queue.GetNextOrder(current); + if (next_order < 0) { + /* no song after this one: stop playback */ + Stop(pc); + + /* reset "current song" */ + current = -1; + } + else + { + if (next_order == 0 && queue.random) { + /* The queue told us that the next song is the first + song. This means we are in repeat mode. Shuffle + the queue order, so this time, the user hears the + songs in a different than before */ + assert(queue.repeat); + + queue.ShuffleOrder(); + + /* note that current and queued are + now invalid, but playlist_play_order() will + discard them anyway */ + } + + PlayOrder(pc, next_order); + } + + /* Consume mode removes each played songs. */ + if (queue.consume) + DeleteOrder(pc, old_current); +} + +void +playlist::PlayPrevious(player_control &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + + int order; + if (current > 0) { + /* play the preceding song */ + order = current - 1; + } else if (queue.repeat) { + /* play the last song in "repeat" mode */ + order = queue.GetLength() - 1; + } else { + /* re-start playing the current song if it's + the first one */ + order = current; + } + + PlayOrder(pc, order); +} + +enum playlist_result +playlist::SeekSongPosition(player_control &pc, unsigned song, float seek_time) +{ + if (!queue.IsValidPosition(song)) + return PLAYLIST_RESULT_BAD_RANGE; + + const Song *queued_song = GetQueuedSong(); + + unsigned i = queue.random + ? queue.PositionToOrder(song) + : song; + + pc.ClearError(); + stop_on_error = true; + error_count = 0; + + if (!playing || (unsigned)current != i) { + /* seeking is not within the current song - prepare + song change */ + + playing = true; + current = i; + + queued_song = nullptr; + } + + Song *the_song = queue.GetOrder(i)->DupDetached(); + if (!pc.Seek(the_song, seek_time)) { + UpdateQueuedSong(pc, queued_song); + + return PLAYLIST_RESULT_NOT_PLAYING; + } + + queued = -1; + UpdateQueuedSong(pc, nullptr); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SeekSongId(player_control &pc, unsigned id, float seek_time) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SeekSongPosition(pc, song, seek_time); +} + +enum playlist_result +playlist::SeekCurrent(player_control &pc, float seek_time, bool relative) +{ + if (!playing) + return PLAYLIST_RESULT_NOT_PLAYING; + + if (relative) { + const auto status = pc.GetStatus(); + + if (status.state != PlayerState::PLAY && + status.state != PlayerState::PAUSE) + return PLAYLIST_RESULT_NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return SeekSongPosition(pc, current, seek_time); +} diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx new file mode 100644 index 000000000..3810d8866 --- /dev/null +++ b/src/PlaylistDatabase.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistDatabase.hxx" +#include "PlaylistVector.hxx" +#include "TextFile.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <string.h> +#include <stdlib.h> + +static constexpr Domain playlist_database_domain("playlist_database"); + +void +playlist_vector_save(FILE *fp, const PlaylistVector &pv) +{ + for (const PlaylistInfo &pi : pv) + fprintf(fp, PLAYLIST_META_BEGIN "%s\n" + "mtime: %li\n" + "playlist_end\n", + pi.name.c_str(), (long)pi.mtime); +} + +bool +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + Error &error) +{ + PlaylistInfo pm(name, 0); + + char *line, *colon; + const char *value; + + while ((line = file.ReadLine()) != nullptr && + strcmp(line, "playlist_end") != 0) { + colon = strchr(line, ':'); + if (colon == nullptr || colon == line) { + error.Format(playlist_database_domain, + "unknown line in db: %s", line); + return false; + } + + *colon++ = 0; + value = strchug_fast_c(colon); + + if (strcmp(line, "mtime") == 0) + pm.mtime = strtol(value, nullptr, 10); + else { + error.Format(playlist_database_domain, + "unknown line in db: %s", line); + return false; + } + } + + pv.UpdateOrInsert(std::move(pm)); + return true; +} diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx new file mode 100644 index 000000000..1481f621f --- /dev/null +++ b/src/PlaylistDatabase.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_DATABASE_HXX + +#include "check.h" + +#include <stdio.h> + +#define PLAYLIST_META_BEGIN "playlist_begin: " + +class PlaylistVector; +class TextFile; +class Error; + +void +playlist_vector_save(FILE *fp, const PlaylistVector &pv); + +bool +playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, + Error &error); + +#endif diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx new file mode 100644 index 000000000..3cb68a1ab --- /dev/null +++ b/src/PlaylistEdit.cxx @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "Song.hxx" +#include "Idle.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Log.hxx" + +#include <stdlib.h> + +void +playlist::OnModified() +{ + queue.IncrementVersion(); + + idle_add(IDLE_PLAYLIST); +} + +void +playlist::Clear(player_control &pc) +{ + Stop(pc); + + queue.Clear(); + current = -1; + + OnModified(); +} + +enum playlist_result +playlist::AppendFile(struct player_control &pc, + const char *path_utf8, unsigned *added_id) +{ + Song *song = Song::LoadFile(path_utf8, nullptr); + if (song == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return AppendSong(pc, song, added_id); +} + +enum playlist_result +playlist::AppendSong(struct player_control &pc, + Song *song, unsigned *added_id) +{ + unsigned id; + + if (queue.IsFull()) + return PLAYLIST_RESULT_TOO_LARGE; + + const Song *const queued_song = GetQueuedSong(); + + id = queue.Append(song, 0); + + if (queue.random) { + /* shuffle the new song into the list of remaining + songs to play */ + + unsigned start; + if (queued >= 0) + start = queued + 1; + else + start = current + 1; + if (start < queue.GetLength()) + queue.ShuffleOrderLast(start, queue.GetLength()); + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + if (added_id) + *added_id = id; + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::AppendURI(struct player_control &pc, + const char *uri, unsigned *added_id) +{ + FormatDebug(playlist_domain, "add to playlist: %s", uri); + + const Database *db = nullptr; + Song *song; + if (uri_has_scheme(uri)) { + song = Song::NewRemote(uri); + } else { + db = GetDatabase(IgnoreError()); + if (db == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + song = db->GetSong(uri, IgnoreError()); + if (song == nullptr) + return PLAYLIST_RESULT_NO_SUCH_SONG; + } + + enum playlist_result result = AppendSong(pc, song, added_id); + if (db != nullptr) + db->ReturnSong(song); + + return result; +} + +enum playlist_result +playlist::SwapPositions(player_control &pc, unsigned song1, unsigned song2) +{ + if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) + return PLAYLIST_RESULT_BAD_RANGE; + + const Song *const queued_song = GetQueuedSong(); + + queue.SwapPositions(song1, song2); + + if (queue.random) { + /* update the queue order, so that current + still points to the current song order */ + + queue.SwapOrders(queue.PositionToOrder(song1), + queue.PositionToOrder(song2)); + } else { + /* correct the "current" song order */ + + if (current == (int)song1) + current = song2; + else if (current == (int)song2) + current = song1; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SwapIds(player_control &pc, unsigned id1, unsigned id2) +{ + int song1 = queue.IdToPosition(id1); + int song2 = queue.IdToPosition(id2); + + if (song1 < 0 || song2 < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SwapPositions(pc, song1, song2); +} + +enum playlist_result +playlist::SetPriorityRange(player_control &pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > GetLength()) + end = GetLength(); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + /* remember "current" and "queued" */ + + const int current_position = GetCurrentPosition(); + const Song *const queued_song = GetQueuedSong(); + + /* apply the priority changes */ + + queue.SetPriorityRange(start, end, priority, current); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + current = queue.PositionToOrder(current_position); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::SetPriorityId(struct player_control &pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue.IdToPosition(song_id); + if (song_position < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return SetPriorityRange(pc, song_position, song_position + 1, + priority); + +} + +void +playlist::DeleteInternal(player_control &pc, + unsigned song, const Song **queued_p) +{ + assert(song < GetLength()); + + unsigned songOrder = queue.PositionToOrder(song); + + if (playing && current == (int)songOrder) { + const bool paused = pc.GetState() == PlayerState::PAUSE; + + /* the current song is going to be deleted: stop the player */ + + pc.Stop(); + playing = false; + + /* see which song is going to be played instead */ + + current = queue.GetNextOrder(current); + if (current == (int)songOrder) + current = -1; + + if (current >= 0 && !paused) + /* play the song after the deleted one */ + PlayOrder(pc, current); + else + /* no songs left to play, stop playback + completely */ + Stop(pc); + + *queued_p = nullptr; + } else if (current == (int)songOrder) + /* there's a "current song" but we're not playing + currently - clear "current" */ + current = -1; + + /* now do it: remove the song */ + + queue.DeletePosition(song); + + /* update the "current" and "queued" variables */ + + if (current > (int)songOrder) + current--; +} + +enum playlist_result +playlist::DeletePosition(struct player_control &pc, unsigned song) +{ + if (song >= queue.GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + const Song *queued_song = GetQueuedSong(); + + DeleteInternal(pc, song, &queued_song); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::DeleteRange(struct player_control &pc, unsigned start, unsigned end) +{ + if (start >= queue.GetLength()) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue.GetLength()) + end = queue.GetLength(); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + const Song *queued_song = GetQueuedSong(); + + do { + DeleteInternal(pc, --end, &queued_song); + } while (end != start); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::DeleteId(struct player_control &pc, unsigned id) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return DeletePosition(pc, song); +} + +void +playlist::DeleteSong(struct player_control &pc, const struct Song &song) +{ + for (int i = queue.GetLength() - 1; i >= 0; --i) + // TODO: compare URI instead of pointer + if (&song == queue.Get(i)) + DeletePosition(pc, i); +} + +enum playlist_result +playlist::MoveRange(player_control &pc, unsigned start, unsigned end, int to) +{ + if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) + return PLAYLIST_RESULT_BAD_RANGE; + + if ((to >= 0 && to + end - start - 1 >= GetLength()) || + (to < 0 && unsigned(abs(to)) > GetLength())) + return PLAYLIST_RESULT_BAD_RANGE; + + if ((int)start == to) + /* nothing happens */ + return PLAYLIST_RESULT_SUCCESS; + + const Song *const queued_song = GetQueuedSong(); + + /* + * (to < 0) => move to offset from current song + * (-playlist.length == to) => move to position BEFORE current song + */ + const int currentSong = GetCurrentPosition(); + if (to < 0) { + if (currentSong < 0) + /* can't move relative to current song, + because there is no current song */ + return PLAYLIST_RESULT_BAD_RANGE; + + if (start <= (unsigned)currentSong && (unsigned)currentSong < end) + /* no-op, can't be moved to offset of itself */ + return PLAYLIST_RESULT_SUCCESS; + to = (currentSong + abs(to)) % GetLength(); + if (start < (unsigned)to) + to--; + } + + queue.MoveRange(start, end, to); + + if (!queue.random) { + /* update current/queued */ + if ((int)start <= current && (unsigned)current < end) + current += to - start; + else if (current >= (int)end && current <= to) + current -= end - start; + else if (current >= to && current < (int)start) + current += end - start; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist::MoveId(player_control &pc, unsigned id1, int to) +{ + int song = queue.IdToPosition(id1); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return MoveRange(pc, song, song + 1, to); +} + +void +playlist::Shuffle(player_control &pc, unsigned start, unsigned end) +{ + if (end > GetLength()) + /* correct the "end" offset */ + end = GetLength(); + + if (start + 1 >= end) + /* needs at least two entries. */ + return; + + const Song *const queued_song = GetQueuedSong(); + if (playing && current >= 0) { + unsigned current_position = queue.OrderToPosition(current); + + if (current_position >= start && current_position < end) { + /* put current playing song first */ + queue.SwapPositions(start, current_position); + + if (queue.random) { + current = queue.PositionToOrder(start); + } else + current = start; + + /* start shuffle after the current song */ + start++; + } + } else { + /* no playback currently: reset current */ + + current = -1; + } + + queue.ShuffleRange(start, end); + + UpdateQueuedSong(pc, queued_song); + OnModified(); +} diff --git a/src/PlaylistError.cxx b/src/PlaylistError.cxx new file mode 100644 index 000000000..91291f551 --- /dev/null +++ b/src/PlaylistError.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistError.hxx" +#include "util/Domain.hxx" + +const Domain playlist_domain("playlist"); diff --git a/src/PlaylistError.hxx b/src/PlaylistError.hxx new file mode 100644 index 000000000..7a3dad749 --- /dev/null +++ b/src/PlaylistError.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ERROR_HXX +#define MPD_PLAYLIST_ERROR_HXX + +class Domain; + +enum playlist_result { + PLAYLIST_RESULT_SUCCESS, + PLAYLIST_RESULT_ERRNO, + PLAYLIST_RESULT_DENIED, + PLAYLIST_RESULT_NO_SUCH_SONG, + PLAYLIST_RESULT_NO_SUCH_LIST, + PLAYLIST_RESULT_LIST_EXISTS, + PLAYLIST_RESULT_BAD_NAME, + PLAYLIST_RESULT_BAD_RANGE, + PLAYLIST_RESULT_NOT_PLAYING, + PLAYLIST_RESULT_TOO_LARGE, + PLAYLIST_RESULT_DISABLED, +}; + +extern const Domain playlist_domain; + +#endif diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx new file mode 100644 index 000000000..e01bc1d26 --- /dev/null +++ b/src/PlaylistFile.cxx @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistFile.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistInfo.hxx" +#include "PlaylistVector.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "Song.hxx" +#include "Mapper.hxx" +#include "TextFile.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "ConfigDefaults.hxx" +#include "Idle.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "fs/DirectoryReader.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <string.h> +#include <errno.h> + +static const char PLAYLIST_COMMENT = '#'; + +static unsigned playlist_max_length; +bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; + +void +spl_global_init(void) +{ + playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH); + + playlist_saveAbsolutePaths = + config_get_bool(CONF_SAVE_ABSOLUTE_PATHS, + DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS); +} + +bool +spl_valid_name(const char *name_utf8) +{ + /* + * Not supporting '/' was done out of laziness, and we should + * really strive to support it in the future. + * + * Not supporting '\r' and '\n' is done out of protocol + * limitations (and arguably laziness), but bending over head + * over heels to modify the protocol (and compatibility with + * all clients) to support idiots who put '\r' and '\n' in + * filenames isn't going to happen, either. + */ + + return strchr(name_utf8, '/') == nullptr && + strchr(name_utf8, '\n') == nullptr && + strchr(name_utf8, '\r') == nullptr; +} + +static const Path & +spl_map(Error &error) +{ + const Path &path_fs = map_spl_path(); + if (path_fs.IsNull()) + error.Set(playlist_domain, PLAYLIST_RESULT_DISABLED, + "Stored playlists are disabled"); + return path_fs; +} + +static bool +spl_check_name(const char *name_utf8, Error &error) +{ + if (!spl_valid_name(name_utf8)) { + error.Set(playlist_domain, PLAYLIST_RESULT_BAD_NAME, + "Bad playlist name"); + return false; + } + + return true; +} + +static Path +spl_map_to_fs(const char *name_utf8, Error &error) +{ + if (spl_map(error).IsNull() || !spl_check_name(name_utf8, error)) + return Path::Null(); + + Path path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs.IsNull()) + error.Set(playlist_domain, PLAYLIST_RESULT_BAD_NAME, + "Bad playlist name"); + + return path_fs; +} + +/** + * Create an #Error for the current errno. + */ +static void +playlist_errno(Error &error) +{ + switch (errno) { + case ENOENT: + error.Set(playlist_domain, PLAYLIST_RESULT_NO_SUCH_LIST, + "No such playlist"); + break; + + default: + error.SetErrno(); + break; + } +} + +static bool +LoadPlaylistFileInfo(PlaylistInfo &info, + const Path &parent_path_fs, const Path &name_fs) +{ + const char *name_fs_str = name_fs.c_str(); + size_t name_length = strlen(name_fs_str); + + if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || + memchr(name_fs_str, '\n', name_length) != nullptr) + return false; + + if (!g_str_has_suffix(name_fs_str, PLAYLIST_FILE_SUFFIX)) + return false; + + Path path_fs = Path::Build(parent_path_fs, name_fs); + struct stat st; + if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) + return false; + + char *name = g_strndup(name_fs_str, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + std::string name_utf8 = Path::ToUTF8(name); + g_free(name); + if (name_utf8.empty()) + return false; + + info.name = std::move(name_utf8); + info.mtime = st.st_mtime; + return true; +} + +PlaylistVector +ListPlaylistFiles(Error &error) +{ + PlaylistVector list; + + const Path &parent_path_fs = spl_map(error); + if (parent_path_fs.IsNull()) + return list; + + DirectoryReader reader(parent_path_fs); + if (reader.HasFailed()) { + error.SetErrno(); + return list; + } + + PlaylistInfo info; + while (reader.ReadEntry()) { + const Path entry = reader.GetEntry(); + if (LoadPlaylistFileInfo(info, parent_path_fs, entry)) + list.push_back(std::move(info)); + } + + return list; +} + +static bool +SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path, + Error &error) +{ + assert(utf8path != nullptr); + + if (spl_map(error).IsNull()) + return false; + + const Path path_fs = spl_map_to_fs(utf8path, error); + if (path_fs.IsNull()) + return false; + + FILE *file = FOpen(path_fs, FOpenMode::WriteText); + if (file == nullptr) { + playlist_errno(error); + return false; + } + + for (const auto &uri_utf8 : contents) + playlist_print_uri(file, uri_utf8.c_str()); + + fclose(file); + return true; +} + +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, Error &error) +{ + PlaylistFileContents contents; + + if (spl_map(error).IsNull()) + return contents; + + const Path path_fs = spl_map_to_fs(utf8path, error); + if (path_fs.IsNull()) + return contents; + + TextFile file(path_fs); + if (file.HasFailed()) { + playlist_errno(error); + return contents; + } + + char *s; + while ((s = file.ReadLine()) != nullptr) { + if (*s == 0 || *s == PLAYLIST_COMMENT) + continue; + + if (g_path_is_absolute(s)) { + const auto path = Path::ToUTF8(s); + if (path.empty()) + continue; + + s = g_strconcat("file://", path.c_str(), NULL); + } else if (!uri_has_scheme(s)) { + char *path_utf8; + + path_utf8 = map_fs_to_utf8(s); + if (path_utf8 == nullptr) + continue; + + s = path_utf8; + } else { + const auto path = Path::ToUTF8(s); + if (path.empty()) + continue; + + s = g_strdup(path.c_str()); + } + + contents.emplace_back(s); + if (contents.size() >= playlist_max_length) + break; + } + + return contents; +} + +bool +spl_move_index(const char *utf8path, unsigned src, unsigned dest, + Error &error) +{ + if (src == dest) + /* this doesn't check whether the playlist exists, but + what the hell.. */ + return true; + + auto contents = LoadPlaylistFile(utf8path, error); + if (contents.empty() && error.IsDefined()) + return false; + + if (src >= contents.size() || dest >= contents.size()) { + error.Set(playlist_domain, PLAYLIST_RESULT_BAD_RANGE, + "Bad range"); + return false; + } + + const auto src_i = std::next(contents.begin(), src); + auto value = std::move(*src_i); + contents.erase(src_i); + + const auto dest_i = std::next(contents.begin(), dest); + contents.insert(dest_i, std::move(value)); + + bool result = SavePlaylistFile(contents, utf8path, error); + + idle_add(IDLE_STORED_PLAYLIST); + return result; +} + +bool +spl_clear(const char *utf8path, Error &error) +{ + if (spl_map(error).IsNull()) + return false; + + const Path path_fs = spl_map_to_fs(utf8path, error); + if (path_fs.IsNull()) + return false; + + FILE *file = FOpen(path_fs, FOpenMode::WriteText); + if (file == nullptr) { + playlist_errno(error); + return false; + } + + fclose(file); + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_delete(const char *name_utf8, Error &error) +{ + const Path path_fs = spl_map_to_fs(name_utf8, error); + if (path_fs.IsNull()) + return false; + + if (!RemoveFile(path_fs)) { + playlist_errno(error); + return false; + } + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_remove_index(const char *utf8path, unsigned pos, Error &error) +{ + auto contents = LoadPlaylistFile(utf8path, error); + if (contents.empty() && error.IsDefined()) + return false; + + if (pos >= contents.size()) { + error.Set(playlist_domain, PLAYLIST_RESULT_BAD_RANGE, + "Bad range"); + return false; + } + + contents.erase(std::next(contents.begin(), pos)); + + bool result = SavePlaylistFile(contents, utf8path, error); + + idle_add(IDLE_STORED_PLAYLIST); + return result; +} + +bool +spl_append_song(const char *utf8path, Song *song, Error &error) +{ + if (spl_map(error).IsNull()) + return false; + + const Path path_fs = spl_map_to_fs(utf8path, error); + if (path_fs.IsNull()) + return false; + + FILE *file = FOpen(path_fs, FOpenMode::AppendText); + if (file == nullptr) { + playlist_errno(error); + return false; + } + + struct stat st; + if (fstat(fileno(file), &st) < 0) { + playlist_errno(error); + fclose(file); + return false; + } + + if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { + fclose(file); + error.Set(playlist_domain, PLAYLIST_RESULT_TOO_LARGE, + "Stored playlist is too large"); + return false; + } + + playlist_print_song(file, song); + + fclose(file); + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_append_uri(const char *url, const char *utf8file, Error &error) +{ + if (uri_has_scheme(url)) { + Song *song = Song::NewRemote(url); + bool success = spl_append_song(utf8file, song, error); + song->Free(); + return success; + } else { + const Database *db = GetDatabase(error); + if (db == nullptr) + return false; + + Song *song = db->GetSong(url, error); + if (song == nullptr) + return false; + + bool success = spl_append_song(utf8file, song, error); + db->ReturnSong(song); + return success; + } +} + +static bool +spl_rename_internal(const Path &from_path_fs, const Path &to_path_fs, + Error &error) +{ + if (!FileExists(from_path_fs)) { + error.Set(playlist_domain, PLAYLIST_RESULT_NO_SUCH_LIST, + "No such playlist"); + return false; + } + + if (FileExists(to_path_fs)) { + error.Set(playlist_domain, PLAYLIST_RESULT_LIST_EXISTS, + "Playlist exists already"); + return false; + } + + if (!RenameFile(from_path_fs, to_path_fs)) { + playlist_errno(error); + return false; + } + + idle_add(IDLE_STORED_PLAYLIST); + return true; +} + +bool +spl_rename(const char *utf8from, const char *utf8to, Error &error) +{ + if (spl_map(error).IsNull()) + return false; + + Path from_path_fs = spl_map_to_fs(utf8from, error); + if (from_path_fs.IsNull()) + return false; + + Path to_path_fs = spl_map_to_fs(utf8to, error); + if (to_path_fs.IsNull()) + return false; + + return spl_rename_internal(from_path_fs, to_path_fs, error); +} diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx new file mode 100644 index 000000000..7d9ab5478 --- /dev/null +++ b/src/PlaylistFile.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_FILE_HXX +#define MPD_PLAYLIST_FILE_HXX + +#include <vector> +#include <string> + +struct Song; +struct PlaylistInfo; +class PlaylistVector; +class Error; + +typedef std::vector<std::string> PlaylistFileContents; + +extern bool playlist_saveAbsolutePaths; + +/** + * Perform some global initialization, e.g. load configuration values. + */ +void +spl_global_init(void); + +/** + * Determines whether the specified string is a valid name for a + * stored playlist. + */ +bool +spl_valid_name(const char *name_utf8); + +/** + * Returns a list of stored_playlist_info struct pointers. Returns + * nullptr if an error occurred. + */ +PlaylistVector +ListPlaylistFiles(Error &error); + +PlaylistFileContents +LoadPlaylistFile(const char *utf8path, Error &error); + +bool +spl_move_index(const char *utf8path, unsigned src, unsigned dest, + Error &error); + +bool +spl_clear(const char *utf8path, Error &error); + +bool +spl_delete(const char *name_utf8, Error &error); + +bool +spl_remove_index(const char *utf8path, unsigned pos, Error &error); + +bool +spl_append_song(const char *utf8path, Song *song, Error &error); + +bool +spl_append_uri(const char *file, const char *utf8file, Error &error); + +bool +spl_rename(const char *utf8from, const char *utf8to, Error &error); + +#endif diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx new file mode 100644 index 000000000..97902275b --- /dev/null +++ b/src/PlaylistGlobal.cxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 manager of the global "struct playlist" instance (g_playlist). + * + */ + +#include "config.h" +#include "PlaylistGlobal.hxx" +#include "Playlist.hxx" +#include "Main.hxx" +#include "Instance.hxx" +#include "GlobalEvents.hxx" + +static void +playlist_tag_event(void) +{ + instance->TagModified(); +} + +static void +playlist_event(void) +{ + instance->SyncWithPlayer(); +} + +void +playlist_global_init() +{ + GlobalEvents::Register(GlobalEvents::TAG, playlist_tag_event); + GlobalEvents::Register(GlobalEvents::PLAYLIST, playlist_event); +} diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx new file mode 100644 index 000000000..4397292db --- /dev/null +++ b/src/PlaylistGlobal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_GLOBAL_HXX +#define MPD_PLAYLIST_GLOBAL_HXX + +void +playlist_global_init(); + +#endif diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx new file mode 100644 index 000000000..96e4f6db9 --- /dev/null +++ b/src/PlaylistInfo.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_INFO_HXX +#define MPD_PLAYLIST_INFO_HXX + +#include "check.h" +#include "gcc.h" + +#include <string> + +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct PlaylistInfo { + /** + * The UTF-8 encoded name of the playlist file. + */ + std::string name; + + time_t mtime; + + class CompareName { + const char *const name; + + public: + constexpr CompareName(const char *_name):name(_name) {} + + gcc_pure + bool operator()(const PlaylistInfo &pi) const { + return pi.name.compare(name) == 0; + } + }; + + PlaylistInfo() = default; + + template<typename N> + PlaylistInfo(N &&_name, time_t _mtime) + :name(std::forward<N>(_name)), mtime(_mtime) {} + + PlaylistInfo(const PlaylistInfo &other) = delete; + PlaylistInfo(PlaylistInfo &&) = default; +}; + +#endif diff --git a/src/PlaylistMapper.cxx b/src/PlaylistMapper.cxx new file mode 100644 index 000000000..b8ea7eb85 --- /dev/null +++ b/src/PlaylistMapper.cxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistMapper.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "util/UriUtil.hxx" + +#include <assert.h> + +static SongEnumerator * +playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + auto playlist = playlist_list_open_uri(path_fs, mutex, cond); + if (playlist != nullptr) + *is_r = nullptr; + else + playlist = playlist_list_open_path(path_fs, mutex, cond, is_r); + + return playlist; +} + +/** + * Load a playlist from the configured playlist directory. + */ +static SongEnumerator * +playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + assert(spl_valid_name(uri)); + + const Path &playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs.IsNull()) + return nullptr; + + const Path uri_fs = Path::FromUTF8(uri); + if (uri_fs.IsNull()) + return nullptr; + + const Path path_fs = Path::Build(playlist_directory_fs, uri_fs); + assert(!path_fs.IsNull()); + + return playlist_open_path(path_fs.c_str(), mutex, cond, is_r); +} + +/** + * Load a playlist from the configured music directory. + */ +static SongEnumerator * +playlist_open_in_music_dir(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + assert(uri_safe_local(uri)); + + Path path = map_uri_fs(uri); + if (path.IsNull()) + return nullptr; + + return playlist_open_path(path.c_str(), mutex, cond, is_r); +} + +SongEnumerator * +playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + if (spl_valid_name(uri)) { + auto playlist = playlist_open_in_playlist_dir(uri, mutex, cond, + is_r); + if (playlist != nullptr) + return playlist; + } + + if (uri_safe_local(uri)) { + auto playlist = playlist_open_in_music_dir(uri, mutex, cond, + is_r); + if (playlist != nullptr) + return playlist; + } + + return nullptr; +} diff --git a/src/PlaylistMapper.hxx b/src/PlaylistMapper.hxx new file mode 100644 index 000000000..b966e6c39 --- /dev/null +++ b/src/PlaylistMapper.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_MAPPER_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +class SongEnumerator; +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 + */ +SongEnumerator * +playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, + struct input_stream **is_r); + +#endif diff --git a/src/PlaylistPlugin.hxx b/src/PlaylistPlugin.hxx new file mode 100644 index 000000000..ce380d886 --- /dev/null +++ b/src/PlaylistPlugin.hxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_PLUGIN_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct config_param; +struct input_stream; +struct Tag; +class SongEnumerator; + +struct playlist_plugin { + const char *name; + + /** + * Initialize the plugin. Optional method. + * + * @param param a configuration block for this plugin, or nullptr + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const config_param ¶m); + + /** + * 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. + */ + SongEnumerator *(*open_uri)(const char *uri, + Mutex &mutex, Cond &cond); + + /** + * Opens the playlist in the specified input stream. It has + * either matched one of the suffixes or one of the MIME + * types. + */ + SongEnumerator *(*open_stream)(struct input_stream *is); + + 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 nullptr 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 config_param ¶m) +{ + return plugin->init != nullptr + ? 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 != nullptr) + plugin->finish(); +} + +static inline SongEnumerator * +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, + Mutex &mutex, Cond &cond) +{ + return plugin->open_uri(uri, mutex, cond); +} + +static inline SongEnumerator * +playlist_plugin_open_stream(const struct playlist_plugin *plugin, + struct input_stream *is) +{ + return plugin->open_stream(is); +} + +#endif diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx new file mode 100644 index 000000000..981cdc82f --- /dev/null +++ b/src/PlaylistPrint.cxx @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistPrint.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "QueuePrint.hxx" +#include "SongEnumerator.hxx" +#include "SongPrint.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Client.hxx" +#include "InputStream.hxx" +#include "Song.hxx" +#include "util/Error.hxx" + +void +playlist_print_uris(Client *client, const struct playlist *playlist) +{ + const struct queue *queue = &playlist->queue; + + queue_print_uris(client, queue, 0, queue->GetLength()); +} + +bool +playlist_print_info(Client *client, const struct playlist *playlist, + unsigned start, unsigned end) +{ + const struct queue *queue = &playlist->queue; + + if (end > queue->GetLength()) + /* correct the "end" offset */ + end = queue->GetLength(); + + if (start > end) + /* an invalid "start" offset is fatal */ + return false; + + queue_print_info(client, queue, start, end); + return true; +} + +bool +playlist_print_id(Client *client, const struct playlist *playlist, + unsigned id) +{ + const struct queue *queue = &playlist->queue; + int position; + + position = queue->IdToPosition(id); + if (position < 0) + /* no such song */ + return false; + + return playlist_print_info(client, playlist, position, position + 1); +} + +bool +playlist_print_current(Client *client, const struct playlist *playlist) +{ + int current_position = playlist->GetCurrentPosition(); + if (current_position < 0) + return false; + + queue_print_info(client, &playlist->queue, + current_position, current_position + 1); + return true; +} + +void +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter) +{ + queue_find(client, &playlist->queue, filter); +} + +void +playlist_print_changes_info(Client *client, + const struct playlist *playlist, + uint32_t version) +{ + queue_print_changes_info(client, &playlist->queue, version); +} + +void +playlist_print_changes_position(Client *client, + const struct playlist *playlist, + uint32_t version) +{ + queue_print_changes_position(client, &playlist->queue, version); +} + +static bool +PrintSongDetails(Client *client, const char *uri_utf8) +{ + const Database *db = GetDatabase(IgnoreError()); + if (db == nullptr) + return false; + + Song *song = db->GetSong(uri_utf8, IgnoreError()); + if (song == nullptr) + return false; + + song_print_info(client, song); + db->ReturnSong(song); + return true; +} + +bool +spl_print(Client *client, const char *name_utf8, bool detail, + Error &error) +{ + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, error); + if (contents.empty() && error.IsDefined()) + return false; + + for (const auto &uri_utf8 : contents) { + if (!detail || !PrintSongDetails(client, uri_utf8.c_str())) + client_printf(client, SONG_FILE "%s\n", + uri_utf8.c_str()); + } + + return true; +} + +static void +playlist_provider_print(Client *client, const char *uri, + SongEnumerator &e, bool detail) +{ + Song *song; + char *base_uri = uri != nullptr ? g_path_get_dirname(uri) : nullptr; + + while ((song = e.NextSong()) != nullptr) { + song = playlist_check_translate_song(song, base_uri, false); + if (song == nullptr) + continue; + + if (detail) + song_print_info(client, song); + else + song_print_uri(client, song); + + song->Free(); + } + + g_free(base_uri); +} + +bool +playlist_file_print(Client *client, const char *uri, bool detail) +{ + Mutex mutex; + Cond cond; + + struct input_stream *is; + SongEnumerator *playlist = playlist_open_any(uri, mutex, cond, &is); + if (playlist == nullptr) + return false; + + playlist_provider_print(client, uri, *playlist, detail); + delete playlist; + + if (is != nullptr) + is->Close(); + + return true; +} diff --git a/src/PlaylistPrint.hxx b/src/PlaylistPrint.hxx new file mode 100644 index 000000000..c8c353d0c --- /dev/null +++ b/src/PlaylistPrint.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PRINT_HXX +#define MPD_PLAYLIST_PRINT_HXX + +#include <stdint.h> + +struct playlist; +class SongFilter; +class Client; +class Error; + +/** + * Sends the whole playlist to the client, song URIs only. + */ +void +playlist_print_uris(Client *client, const struct playlist *playlist); + +/** + * Sends a range of the playlist to the client, including all known + * information about the songs. The "end" offset is decreased + * automatically if it is too large; passing UINT_MAX is allowed. + * This function however fails when the start offset is invalid. + */ +bool +playlist_print_info(Client *client, const struct playlist *playlist, + unsigned start, unsigned end); + +/** + * Sends the song with the specified id to the client. + * + * @return true on suite, false if there is no such song + */ +bool +playlist_print_id(Client *client, const struct playlist *playlist, + unsigned id); + +/** + * Sends the current song to the client. + * + * @return true on success, false if there is no current song + */ +bool +playlist_print_current(Client *client, const struct playlist *playlist); + +/** + * Find songs in the playlist. + */ +void +playlist_print_find(Client *client, const struct playlist *playlist, + const SongFilter &filter); + +/** + * Print detailed changes since the specified playlist version. + */ +void +playlist_print_changes_info(Client *client, + const struct playlist *playlist, + uint32_t version); + +/** + * Print changes since the specified playlist version, position only. + */ +void +playlist_print_changes_position(Client *client, + const struct playlist *playlist, + uint32_t version); + +/** + * Send the stored playlist to the client. + * + * @param client the client which requested the playlist + * @param name_utf8 the name of the stored playlist 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 +spl_print(Client *client, const char *name_utf8, bool detail, + Error &error); + +/** + * 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(Client *client, const char *uri, bool detail); + +#endif diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx new file mode 100644 index 000000000..2b9e346ea --- /dev/null +++ b/src/PlaylistQueue.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistQueue.hxx" +#include "PlaylistPlugin.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" +#include "InputStream.hxx" +#include "SongEnumerator.hxx" +#include "Song.hxx" + +enum playlist_result +playlist_load_into_queue(const char *uri, SongEnumerator &e, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure) +{ + enum playlist_result result; + Song *song; + char *base_uri = uri != nullptr ? g_path_get_dirname(uri) : nullptr; + + for (unsigned i = 0; + i < end_index && (song = e.NextSong()) != nullptr; + ++i) { + if (i < start_index) { + /* skip songs before the start index */ + song->Free(); + continue; + } + + song = playlist_check_translate_song(song, base_uri, secure); + if (song == nullptr) + continue; + + result = dest->AppendSong(*pc, song); + song->Free(); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_free(base_uri); + return result; + } + } + + g_free(base_uri); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure) +{ + Mutex mutex; + Cond cond; + + struct input_stream *is; + auto playlist = playlist_open_any(uri, mutex, cond, &is); + if (playlist == nullptr) + return PLAYLIST_RESULT_NO_SUCH_LIST; + + enum playlist_result result = + playlist_load_into_queue(uri, *playlist, + start_index, end_index, + dest, pc, secure); + delete playlist; + + if (is != nullptr) + is->Close(); + + return result; +} diff --git a/src/PlaylistQueue.hxx b/src/PlaylistQueue.hxx new file mode 100644 index 000000000..71768ecb4 --- /dev/null +++ b/src/PlaylistQueue.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_QUEUE_HXX + +#include "PlaylistError.hxx" + +class SongEnumerator; +struct playlist; +struct player_control; + +/** + * 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 + * @param start_index the index of the first song + * @param end_index the index of the last song (excluding) + */ +enum playlist_result +playlist_load_into_queue(const char *uri, SongEnumerator &e, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +enum playlist_result +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure); + +#endif + diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx new file mode 100644 index 000000000..fa33596f0 --- /dev/null +++ b/src/PlaylistRegistry.cxx @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "playlist/ExtM3uPlaylistPlugin.hxx" +#include "playlist/M3uPlaylistPlugin.hxx" +#include "playlist/XspfPlaylistPlugin.hxx" +#include "playlist/DespotifyPlaylistPlugin.hxx" +#include "playlist/SoundCloudPlaylistPlugin.hxx" +#include "playlist/PlsPlaylistPlugin.hxx" +#include "playlist/AsxPlaylistPlugin.hxx" +#include "playlist/RssPlaylistPlugin.hxx" +#include "playlist/CuePlaylistPlugin.hxx" +#include "playlist/EmbeddedCuePlaylistPlugin.hxx" +#include "InputStream.hxx" +#include "util/UriUtil.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigData.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +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_DESPOTIFY + &despotify_playlist_plugin, +#endif +#ifdef ENABLE_SOUNDCLOUD + &soundcloud_playlist_plugin, +#endif + &cue_playlist_plugin, + &embcue_playlist_plugin, + nullptr +}; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; + +#define playlist_plugins_for_each_enabled(plugin) \ + playlist_plugins_for_each(plugin) \ + if (playlist_plugins_enabled[playlist_plugin_iterator - 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 nullptr if none was configured + */ +static const struct config_param * +playlist_plugin_config(const char *plugin_name) +{ + const struct config_param *param = nullptr; + + assert(plugin_name != nullptr); + + while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != nullptr) { + const char *name = param->GetBlockValue("name"); + if (name == nullptr) + FormatFatalError("playlist configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return nullptr; +} + +void +playlist_list_global_init(void) +{ + const config_param empty; + + for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + const struct config_param *param = + playlist_plugin_config(plugin->name); + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("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) +{ + playlist_plugins_for_each_enabled(plugin) + playlist_plugin_finish(plugin); +} + +static SongEnumerator * +playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, + bool *tried) +{ + char *scheme; + SongEnumerator *playlist = nullptr; + + assert(uri != nullptr); + + scheme = g_uri_parse_scheme(uri); + if (scheme == nullptr) + return nullptr; + + for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + assert(!tried[i]); + + if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr && + plugin->schemes != nullptr && + string_array_contains(plugin->schemes, scheme)) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != nullptr) + break; + + tried[i] = true; + } + } + + g_free(scheme); + return playlist; +} + +static SongEnumerator * +playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond, + const bool *tried) +{ + const char *suffix; + SongEnumerator *playlist = nullptr; + + assert(uri != nullptr); + + suffix = uri_get_suffix(uri); + if (suffix == nullptr) + return nullptr; + + for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && !tried[i] && + plugin->open_uri != nullptr && plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != nullptr) + break; + } + } + + return playlist; +} + +SongEnumerator * +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + /** 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 != nullptr); + + memset(tried, false, sizeof(tried)); + + auto playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried); + if (playlist == nullptr) + playlist = playlist_list_open_uri_suffix(uri, mutex, cond, + tried); + + return playlist; +} + +static SongEnumerator * +playlist_list_open_stream_mime2(struct input_stream *is, const char *mime) +{ + assert(is != nullptr); + assert(mime != nullptr); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->open_stream != nullptr && + plugin->mime_types != nullptr && + string_array_contains(plugin->mime_types, mime)) { + /* rewind the stream, so each plugin gets a + fresh start */ + is->Seek(0, SEEK_SET, IgnoreError()); + + auto playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != nullptr) + return playlist; + } + } + + return nullptr; +} + +static SongEnumerator * +playlist_list_open_stream_mime(struct input_stream *is, const char *full_mime) +{ + assert(full_mime != nullptr); + + const char *semicolon = strchr(full_mime, ';'); + if (semicolon == nullptr) + return playlist_list_open_stream_mime2(is, full_mime); + + if (semicolon == full_mime) + return nullptr; + + /* probe only the portion before the semicolon*/ + char *mime = g_strndup(full_mime, semicolon - full_mime); + auto playlist = playlist_list_open_stream_mime2(is, mime); + g_free(mime); + return playlist; +} + +static SongEnumerator * +playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix) +{ + assert(is != nullptr); + assert(suffix != nullptr); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->open_stream != nullptr && + plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) { + /* rewind the stream, so each plugin gets a + fresh start */ + is->Seek(0, SEEK_SET, IgnoreError()); + + auto playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != nullptr) + return playlist; + } + } + + return nullptr; +} + +SongEnumerator * +playlist_list_open_stream(struct input_stream *is, const char *uri) +{ + const char *suffix; + + is->LockWaitReady(); + + const char *const mime = is->GetMimeType(); + if (mime != nullptr) { + auto playlist = playlist_list_open_stream_mime(is, mime); + if (playlist != nullptr) + return playlist; + } + + suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr; + if (suffix != nullptr) { + auto playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != nullptr) + return playlist; + } + + return nullptr; +} + +bool +playlist_suffix_supported(const char *suffix) +{ + assert(suffix != nullptr); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) + return true; + } + + return false; +} + +SongEnumerator * +playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + struct input_stream **is_r) +{ + const char *suffix; + + assert(path_fs != nullptr); + + suffix = uri_get_suffix(path_fs); + if (suffix == nullptr || !playlist_suffix_supported(suffix)) + return nullptr; + + Error error; + input_stream *is = input_stream::Open(path_fs, mutex, cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + is->LockWaitReady(); + + auto playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != nullptr) + *is_r = is; + else + is->Close(); + + return playlist; +} diff --git a/src/PlaylistRegistry.hxx b/src/PlaylistRegistry.hxx new file mode 100644 index 000000000..aa6c36b85 --- /dev/null +++ b/src/PlaylistRegistry.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_REGISTRY_HXX +#define MPD_PLAYLIST_REGISTRY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +class SongEnumerator; +struct input_stream; + +extern const struct playlist_plugin *const playlist_plugins[]; + +#define playlist_plugins_for_each(plugin) \ + for (const struct playlist_plugin *plugin, \ + *const*playlist_plugin_iterator = &playlist_plugins[0]; \ + (plugin = *playlist_plugin_iterator) != nullptr; \ + ++playlist_plugin_iterator) + +/** + * 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. + */ +SongEnumerator * +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond); + +/** + * 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 + */ +SongEnumerator * +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 nullptr on error + */ +SongEnumerator * +playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + struct input_stream **is_r); + +#endif diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx new file mode 100644 index 000000000..6e17b6fc3 --- /dev/null +++ b/src/PlaylistSave.cxx @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistError.hxx" +#include "Playlist.hxx" +#include "Song.hxx" +#include "Mapper.hxx" +#include "Idle.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string.h> + +void +playlist_print_song(FILE *file, const Song *song) +{ + if (playlist_saveAbsolutePaths && song->IsInDatabase()) { + const Path path = map_song_fs(song); + if (!path.IsNull()) + fprintf(file, "%s\n", path.c_str()); + } else { + char *uri = song->GetURI(); + const Path uri_fs = Path::FromUTF8(uri); + g_free(uri); + + if (!uri_fs.IsNull()) + fprintf(file, "%s\n", uri_fs.c_str()); + } +} + +void +playlist_print_uri(FILE *file, const char *uri) +{ + Path path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) && + !g_path_is_absolute(uri) + ? map_uri_fs(uri) + : Path::FromUTF8(uri); + + if (!path.IsNull()) + fprintf(file, "%s\n", path.c_str()); +} + +enum playlist_result +spl_save_queue(const char *name_utf8, const struct queue *queue) +{ + if (map_spl_path().IsNull()) + return PLAYLIST_RESULT_DISABLED; + + if (!spl_valid_name(name_utf8)) + return PLAYLIST_RESULT_BAD_NAME; + + const Path path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs.IsNull()) + return PLAYLIST_RESULT_BAD_NAME; + + if (FileExists(path_fs)) + return PLAYLIST_RESULT_LIST_EXISTS; + + FILE *file = FOpen(path_fs, FOpenMode::WriteText); + + if (file == nullptr) + return PLAYLIST_RESULT_ERRNO; + + for (unsigned i = 0; i < queue->GetLength(); i++) + playlist_print_song(file, queue->Get(i)); + + fclose(file); + + idle_add(IDLE_STORED_PLAYLIST); + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +spl_save_playlist(const char *name_utf8, const struct playlist *playlist) +{ + return spl_save_queue(name_utf8, &playlist->queue); +} + +bool +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8, + unsigned start_index, unsigned end_index, + Error &error) +{ + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, error); + if (contents.empty() && error.IsDefined()) + return false; + + if (end_index > contents.size()) + end_index = contents.size(); + + for (unsigned i = start_index; i < end_index; ++i) { + const auto &uri_utf8 = contents[i]; + + if (memcmp(uri_utf8.c_str(), "file:///", 8) == 0) { + const char *path_utf8 = uri_utf8.c_str() + 7; + + if (playlist->AppendFile(*pc, path_utf8) != PLAYLIST_RESULT_SUCCESS) + g_warning("can't add file \"%s\"", path_utf8); + continue; + } + + if ((playlist->AppendURI(*pc, uri_utf8.c_str())) != PLAYLIST_RESULT_SUCCESS) { + /* for windows compatibility, convert slashes */ + char *temp2 = g_strdup(uri_utf8.c_str()); + char *p = temp2; + while (*p) { + if (*p == '\\') + *p = '/'; + p++; + } + + if (playlist->AppendURI(*pc, temp2) != PLAYLIST_RESULT_SUCCESS) + FormatError(playlist_domain, + "can't add file \"%s\"", temp2); + + g_free(temp2); + } + } + + return true; +} diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx new file mode 100644 index 000000000..f85558755 --- /dev/null +++ b/src/PlaylistSave.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_SAVE_H +#define MPD_PLAYLIST_SAVE_H + +#include "PlaylistError.hxx" + +#include <stdio.h> + +struct Song; +struct queue; +struct playlist; +struct player_control; +class Error; + +void +playlist_print_song(FILE *fp, const Song *song); + +void +playlist_print_uri(FILE *fp, const char *uri); + +/** + * Saves a queue object into a stored playlist file. + */ +enum playlist_result +spl_save_queue(const char *name_utf8, const struct queue *queue); + +/** + * Saves a playlist object into a stored playlist file. + */ +enum playlist_result +spl_save_playlist(const char *name_utf8, const struct playlist *playlist); + +/** + * Loads a stored playlist file, and append all songs to the global + * playlist. + */ +bool +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8, + unsigned start_index, unsigned end_index, + Error &error); + +#endif diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx new file mode 100644 index 000000000..00735ac60 --- /dev/null +++ b/src/PlaylistSong.cxx @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistSong.hxx" +#include "Mapper.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "ls.hxx" +#include "tag/Tag.hxx" +#include "fs/Path.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "Song.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static void +merge_song_metadata(Song *dest, const Song *base, + const Song *add) +{ + dest->tag = base->tag != nullptr + ? (add->tag != nullptr + ? Tag::Merge(*base->tag, *add->tag) + : new Tag(*base->tag)) + : (add->tag != nullptr + ? new Tag(*add->tag) + : nullptr); + + dest->mtime = base->mtime; + dest->start_ms = add->start_ms; + dest->end_ms = add->end_ms; +} + +static Song * +apply_song_metadata(Song *dest, const Song *src) +{ + Song *tmp; + + assert(dest != nullptr); + assert(src != nullptr); + + if (src->tag == nullptr && src->start_ms == 0 && src->end_ms == 0) + return dest; + + if (dest->IsInDatabase()) { + const Path &path_fs = map_song_fs(dest); + if (path_fs.IsNull()) + return dest; + + std::string path_utf8 = path_fs.ToUTF8(); + if (path_utf8.empty()) + path_utf8 = path_fs.c_str(); + + tmp = Song::NewFile(path_utf8.c_str(), nullptr); + + merge_song_metadata(tmp, dest, src); + } else { + tmp = Song::NewFile(dest->uri, nullptr); + merge_song_metadata(tmp, dest, src); + } + + if (dest->tag != nullptr && 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; + + dest->Free(); + return tmp; +} + +static Song * +playlist_check_load_song(const Song *song, const char *uri, bool secure) +{ + Song *dest; + + if (uri_has_scheme(uri)) { + dest = Song::NewRemote(uri); + } else if (g_path_is_absolute(uri) && secure) { + dest = Song::LoadFile(uri, nullptr); + if (dest == nullptr) + return nullptr; + } else { + const Database *db = GetDatabase(IgnoreError()); + if (db == nullptr) + return nullptr; + + Song *tmp = db->GetSong(uri, IgnoreError()); + if (tmp == nullptr) + /* not found in database */ + return nullptr; + + dest = tmp->DupDetached(); + db->ReturnSong(tmp); + } + + return apply_song_metadata(dest, song); +} + +Song * +playlist_check_translate_song(Song *song, const char *base_uri, + bool secure) +{ + if (song->IsInDatabase()) + /* already ok */ + return song; + + const 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(); + return nullptr; + } + } + + if (base_uri != nullptr && strcmp(base_uri, ".") == 0) + /* g_path_get_dirname() returns "." when there is no + directory name in the given path; clear that now, + because it would break the database lookup + functions */ + base_uri = nullptr; + + if (g_path_is_absolute(uri)) { + /* XXX fs_charset vs utf8? */ + const char *suffix = map_to_relative_path(uri); + assert(suffix != nullptr); + + if (suffix != uri) + uri = suffix; + else if (!secure) { + /* local files must be relative to the music + directory when "secure" is enabled */ + song->Free(); + return nullptr; + } + + base_uri = nullptr; + } + + char *allocated = nullptr; + if (base_uri != nullptr) + uri = allocated = g_build_filename(base_uri, uri, nullptr); + + Song *dest = playlist_check_load_song(song, uri, secure); + song->Free(); + g_free(allocated); + return dest; +} diff --git a/src/PlaylistSong.hxx b/src/PlaylistSong.hxx new file mode 100644 index 000000000..d0db99868 --- /dev/null +++ b/src/PlaylistSong.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_SONG_HXX + +struct Song; + +/** + * Verifies the song, returns nullptr if it is unsafe. Translate the + * song to a new song object within the database, if it is a local + * file. The old song object is freed. + * + * @param secure if true, then local files are only allowed if they + * are relative to base_uri + */ +Song * +playlist_check_translate_song(Song *song, const char *base_uri, + bool secure); + +#endif diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx new file mode 100644 index 000000000..49e9dfb49 --- /dev/null +++ b/src/PlaylistState.cxx @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#include "config.h" +#include "PlaylistState.hxx" +#include "PlaylistError.hxx" +#include "Playlist.hxx" +#include "QueueSave.hxx" +#include "TextFile.hxx" +#include "PlayerControl.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "Log.hxx" + +#include <string.h> +#include <stdlib.h> + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_SINGLE "single: " +#define PLAYLIST_STATE_FILE_CONSUME "consume: " +#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" + +#define PLAYLIST_STATE_FILE_STATE_PLAY "play" +#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" +#define PLAYLIST_STATE_FILE_STATE_STOP "stop" + +#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX + +void +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc) +{ + const auto player_status = pc->GetStatus(); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); + + if (playlist->playing) { + switch (player_status.state) { + case PlayerState::PAUSE: + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); + break; + default: + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); + } + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist->queue.OrderToPosition(playlist->current)); + 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", + playlist->queue.OrderToPosition(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, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)pc->GetCrossFade()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", + pc->GetMixRampDb()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc->GetMixRampDelay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); + queue_save(fp, &playlist->queue); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); +} + +static void +playlist_state_load(TextFile &file, struct playlist *playlist) +{ + const char *line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, "No playlist in state file"); + return; + } + + while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(file, line, &playlist->queue); + + line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, + "'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); + break; + } + } + + playlist->queue.IncrementVersion(); +} + +bool +playlist_state_restore(const char *line, TextFile &file, + struct playlist *playlist, struct player_control *pc) +{ + int current = -1; + int seek_time = 0; + bool random_mode = false; + + if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + return false; + + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + PlayerState state; + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PlayerState::PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PlayerState::PAUSE; + else + state = PlayerState::STOP; + + while ((line = file.ReadLine()) != nullptr) { + if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { + seek_time = + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { + playlist->SetRepeat(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { + playlist->SetSingle(*pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { + playlist->SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc->SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc->SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + pc->SetMixRampDelay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { + random_mode = + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), + "1") == 0; + } 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(line, + PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { + playlist_state_load(file, playlist); + } + } + + playlist->SetRandom(*pc, random_mode); + + if (!playlist->queue.IsEmpty()) { + if (!playlist->queue.IsValidPosition(current)) + current = 0; + + if (state == PlayerState::PLAY && + config_get_bool(CONF_RESTORE_PAUSED, false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PlayerState::PAUSE; + + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PlayerState::STOP) + pc->UpdateAudio(); + + if (state == PlayerState::STOP /* && config_option */) + playlist->current = current; + else if (seek_time == 0) + playlist->PlayPosition(*pc, current); + else + playlist->SeekSongPosition(*pc, current, seek_time); + + if (state == PlayerState::PAUSE) + pc->Pause(); + } + + return true; +} + +unsigned +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc) +{ + const auto player_status = pc->GetStatus(); + + return playlist->queue.version ^ + (player_status.state != PlayerState::STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist->current >= 0 + ? (playlist->queue.OrderToPosition(playlist->current) << 16) + : 0) ^ + ((int)pc->GetCrossFade() << 20) ^ + (unsigned(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/PlaylistState.hxx b/src/PlaylistState.hxx new file mode 100644 index 000000000..7e9944789 --- /dev/null +++ b/src/PlaylistState.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX + +#include <stdio.h> + +struct playlist; +struct player_control; +class TextFile; + +void +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc); + +bool +playlist_state_restore(const char *line, TextFile &file, + struct playlist *playlist, struct player_control *pc); + +/** + * 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, + struct player_control *pc); + +#endif diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx new file mode 100644 index 000000000..68657af50 --- /dev/null +++ b/src/PlaylistVector.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlaylistVector.hxx" +#include "DatabaseLock.hxx" + +#include <algorithm> + +#include <assert.h> +#include <string.h> +#include <glib.h> + +PlaylistVector::iterator +PlaylistVector::find(const char *name) +{ + assert(holding_db_lock()); + assert(name != nullptr); + + return std::find_if(begin(), end(), + PlaylistInfo::CompareName(name)); +} + +bool +PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi) +{ + assert(holding_db_lock()); + + auto i = find(pi.name.c_str()); + if (i != end()) { + if (pi.mtime == i->mtime) + return false; + + i->mtime = pi.mtime; + } else + push_back(std::move(pi)); + + return true; +} + +bool +PlaylistVector::erase(const char *name) +{ + assert(holding_db_lock()); + + auto i = find(name); + if (i == end()) + return false; + + erase(i); + return true; +} diff --git a/src/PlaylistVector.hxx b/src/PlaylistVector.hxx new file mode 100644 index 000000000..d10c90fda --- /dev/null +++ b/src/PlaylistVector.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PLAYLIST_VECTOR_HXX + +#include "PlaylistInfo.hxx" +#include "gcc.h" + +#include <list> + +class PlaylistVector : protected std::list<PlaylistInfo> { +protected: + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + iterator find(const char *name); + +public: + using std::list<PlaylistInfo>::empty; + using std::list<PlaylistInfo>::begin; + using std::list<PlaylistInfo>::end; + using std::list<PlaylistInfo>::push_back; + using std::list<PlaylistInfo>::erase; + + /** + * Caller must lock the #db_mutex. + * + * @return true if the vector or one of its items was modified + */ + bool UpdateOrInsert(PlaylistInfo &&pi); + + /** + * Caller must lock the #db_mutex. + */ + bool erase(const char *name); +}; + +#endif /* SONGVEC_H */ diff --git a/src/Queue.cxx b/src/Queue.cxx new file mode 100644 index 000000000..6bb8175a1 --- /dev/null +++ b/src/Queue.cxx @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Queue.hxx" +#include "Song.hxx" + +#include <stdlib.h> + +queue::queue(unsigned _max_length) + :max_length(_max_length), length(0), + version(1), + items(new Item[max_length]), + order(new unsigned[max_length]), + id_table(max_length * HASH_MULT), + repeat(false), + single(false), + consume(false), + random(false) +{ +} + +queue::~queue() +{ + Clear(); + + delete[] items; + delete[] order; +} + +int +queue::GetNextOrder(unsigned _order) const +{ + assert(_order < length); + + if (single && repeat && !consume) + return _order; + else if (_order + 1 < length) + return _order + 1; + else if (repeat && (_order > 0 || !consume)) + /* restart at first song */ + return 0; + else + /* end of queue */ + return -1; +} + +void +queue::IncrementVersion() +{ + static unsigned long max = ((uint32_t) 1 << 31) - 1; + + version++; + + if (version >= max) { + for (unsigned i = 0; i < length; i++) + items[i].version = 0; + + version = 1; + } +} + +void +queue::ModifyAtOrder(unsigned _order) +{ + assert(_order < length); + + unsigned position = order[_order]; + items[position].version = version; + + IncrementVersion(); +} + +void +queue::ModifyAll() +{ + for (unsigned i = 0; i < length; i++) + items[i].version = version; + + IncrementVersion(); +} + +unsigned +queue::Append(Song *song, uint8_t priority) +{ + assert(!IsFull()); + + const unsigned position = length++; + const unsigned id = id_table.Insert(position); + + auto &item = items[position]; + item.song = song->DupDetached(); + item.id = id; + item.version = version; + item.priority = priority; + + order[position] = position; + + return id; +} + +void +queue::SwapPositions(unsigned position1, unsigned position2) +{ + unsigned id1 = items[position1].id; + unsigned id2 = items[position2].id; + + std::swap(items[position1], items[position2]); + + items[position1].version = version; + items[position2].version = version; + + id_table.Move(id1, position2); + id_table.Move(id2, position1); +} + +void +queue::MovePostion(unsigned from, unsigned to) +{ + const Item tmp = items[from]; + + /* move songs to one less in from->to */ + + for (unsigned i = from; i < to; i++) + MoveItemTo(i + 1, i); + + /* move songs to one more in to->from */ + + for (unsigned i = from; i > to; i--) + MoveItemTo(i - 1, i); + + /* put song at _to_ */ + + id_table.Move(tmp.id, to); + items[to] = tmp; + items[to].version = version; + + /* now deal with order */ + + if (random) { + for (unsigned i = 0; i < length; i++) { + if (order[i] > from && order[i] <= to) + order[i]--; + else if (order[i] < from && + order[i] >= to) + order[i]++; + else if (from == order[i]) + order[i] = to; + } + } +} + +void +queue::MoveRange(unsigned start, unsigned end, unsigned to) +{ + Item tmp[end - start]; + // Copy the original block [start,end-1] + for (unsigned i = start; i < end; i++) + tmp[i - start] = items[i]; + + // If to > start, we need to move to-start items to start, starting from end + for (unsigned i = end; i < end + to - start; i++) + MoveItemTo(i, start + i - end); + + // If to < start, we need to move start-to items to newend (= end + to - start), starting from to + // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 + // We have to iterate in this order to avoid writing over something we haven't yet moved + for (int i = start - 1; i >= int(to); i--) + MoveItemTo(i, i + end - start); + + // Copy the original block back in, starting at to. + for (unsigned i = start; i< end; i++) + { + id_table.Move(tmp[i - start].id, to + i - start); + items[to + i - start] = tmp[i-start]; + items[to + i - start].version = version; + } + + if (random) { + // Update the positions in the queue. + // Note that the ranges for these cases are the same as the ranges of + // the loops above. + for (unsigned i = 0; i < length; i++) { + if (order[i] >= end && order[i] < to + end - start) + order[i] -= end - start; + else if (order[i] < start && + order[i] >= to) + order[i] += end - start; + else if (start <= order[i] && order[i] < end) + order[i] += to - start; + } + } +} + +void +queue::MoveOrder(unsigned from_order, unsigned to_order) +{ + assert(from_order < length); + assert(to_order <= length); + + const unsigned from_position = OrderToPosition(from_order); + + if (from_order < to_order) { + for (unsigned i = from_order; i < to_order; ++i) + order[i] = order[i + 1]; + } else { + for (unsigned i = from_order; i > to_order; --i) + order[i] = order[i - 1]; + } + + order[to_order] = from_position; +} + +void +queue::DeletePosition(unsigned position) +{ + assert(position < length); + + Song *song = Get(position); + assert(!song->IsInDatabase() || song->IsDetached()); + song->Free(); + + const unsigned id = PositionToId(position); + const unsigned _order = PositionToOrder(position); + + --length; + + /* release the song id */ + + id_table.Erase(id); + + /* delete song from songs array */ + + for (unsigned i = position; i < length; i++) + MoveItemTo(i + 1, i); + + /* delete the entry from the order array */ + + for (unsigned i = _order; i < length; i++) + order[i] = order[i + 1]; + + /* readjust values in the order array */ + + for (unsigned i = 0; i < length; i++) + if (order[i] > position) + --order[i]; +} + +void +queue::Clear() +{ + for (unsigned i = 0; i < length; i++) { + Item *item = &items[i]; + + assert(!item->song->IsInDatabase() || + item->song->IsDetached()); + item->song->Free(); + + id_table.Erase(item->id); + } + + length = 0; +} + +static void +queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + auto cmp = [queue](unsigned a_pos, unsigned b_pos){ + const queue::Item &a = queue->items[a_pos]; + const queue::Item &b = queue->items[b_pos]; + + return a.priority > b.priority; + }; + + std::stable_sort(queue->order + start, queue->order + end, cmp); +} + +void +queue::ShuffleOrderRange(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + std::shuffle(order + start, order + end, rand); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(this, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = GetOrderPriority(start); + + for (unsigned i = start + 1; i < end; ++i) { + const uint8_t priority = GetOrderPriority(i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + ShuffleOrderRange(group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + ShuffleOrderRange(group_start, end); +} + +void +queue::ShuffleOrder() +{ + ShuffleOrderRangeWithPriority(0, length); +} + +void +queue::ShuffleOrderFirst(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(start, distribution(rand)); +} + +void +queue::ShuffleOrderLast(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(end - 1, distribution(rand)); +} + +void +queue::ShuffleRange(unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + + for (unsigned i = start; i < end; i++) { + std::uniform_int_distribution<unsigned> distribution(start, + end - 1); + unsigned ri = distribution(rand); + SwapPositions(i, ri); + } +} + +unsigned +queue::FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority <= priority && i != exclude_order) + return i; + } + + return length; +} + +unsigned +queue::CountSamePriority(unsigned start_order, uint8_t priority) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority != priority) + return i - start_order; + } + + return length - start_order; +} + +bool +queue::SetPriority(unsigned position, uint8_t priority, int after_order) +{ + assert(position < length); + + Item *item = &items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = version; + item->priority = priority; + + if (!random) + /* don't reorder if not in random mode */ + return true; + + unsigned _order = PositionToOrder(position); + if (after_order >= 0) { + if (_order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (_order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + OrderToPosition(after_order); + const Item *after_item = + &items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + FindPriorityOrder(after_order + 1, priority, _order); + const unsigned new_order = before_order > _order + ? before_order - 1 + : before_order; + MoveOrder(_order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = CountSamePriority(new_order, priority); + assert(priority_count >= 1); + ShuffleOrderFirst(new_order, new_order + priority_count); + + return true; +} + +bool +queue::SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(start_position <= end_position); + assert(end_position <= length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)OrderToPosition(after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)PositionToOrder(after_position) + : -1; + + modified |= SetPriority(i, priority, after_order); + } + + return modified; +} diff --git a/src/Queue.hxx b/src/Queue.hxx new file mode 100644 index 000000000..d8eeb271e --- /dev/null +++ b/src/Queue.hxx @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_QUEUE_HXX +#define MPD_QUEUE_HXX + +#include "gcc.h" +#include "IdTable.hxx" +#include "util/LazyRandomEngine.hxx" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> + +struct Song; + +/** + * A queue of songs. This is the backend of the playlist: it contains + * an ordered list of songs. + * + * Songs can be addressed in three possible ways: + * + * - the position in the queue + * - the unique id (which stays the same, regardless of moves) + * - the order number (which only differs from "position" in random mode) + */ +struct queue { + /** + * reserve max_length * HASH_MULT elements in the id + * number space + */ + static constexpr unsigned HASH_MULT = 4; + + /** + * One element of the queue: basically a song plus some queue specific + * information attached. + */ + struct Item { + Song *song; + + /** the unique id of this item in the queue */ + unsigned id; + + /** when was this item last changed? */ + uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; + }; + + /** configured maximum length of the queue */ + unsigned max_length; + + /** number of songs in the queue */ + unsigned length; + + /** the current version number */ + uint32_t version; + + /** all songs in "position" order */ + Item *items; + + /** map order numbers to positions */ + unsigned *order; + + /** map song ids to positions */ + IdTable id_table; + + /** repeat playback when the end of the queue has been + reached? */ + bool repeat; + + /** play only current song. */ + bool single; + + /** remove each played files. */ + bool consume; + + /** play back songs in random order? */ + bool random; + + /** random number generator for shuffle and random mode */ + LazyRandomEngine rand; + + queue(unsigned max_length); + + /** + * Deinitializes a queue object. It does not free the queue + * pointer itself. + */ + ~queue(); + + queue(const queue &other) = delete; + queue &operator=(const queue &other) = delete; + + unsigned GetLength() const { + assert(length <= max_length); + + return length; + } + + /** + * Determine if the queue is empty, i.e. there are no songs. + */ + bool IsEmpty() const { + return length == 0; + } + + /** + * Determine if the maximum number of songs has been reached. + */ + bool IsFull() const { + assert(length <= max_length); + + return length >= max_length; + } + + /** + * Is that a valid position number? + */ + bool IsValidPosition(unsigned position) const { + return position < length; + } + + /** + * Is that a valid order number? + */ + bool IsValidOrder(unsigned _order) const { + return _order < length; + } + + int IdToPosition(unsigned id) const { + return id_table.IdToPosition(id); + } + + int PositionToId(unsigned position) const + { + assert(position < length); + + return items[position].id; + } + + gcc_pure + unsigned OrderToPosition(unsigned _order) const { + assert(_order < length); + + return order[_order]; + } + + gcc_pure + unsigned PositionToOrder(unsigned position) const { + assert(position < length); + + for (unsigned i = 0;; ++i) { + assert(i < length); + + if (order[i] == position) + return i; + } + } + + gcc_pure + uint8_t GetPriorityAtPosition(unsigned position) const { + assert(position < length); + + return items[position].priority; + } + + const Item &GetOrderItem(unsigned i) const { + assert(IsValidOrder(i)); + + return items[OrderToPosition(i)]; + } + + uint8_t GetOrderPriority(unsigned i) const { + return GetOrderItem(i).priority; + } + + /** + * Returns the song at the specified position. + */ + Song *Get(unsigned position) const { + assert(position < length); + + return items[position].song; + } + + /** + * Returns the song at the specified order number. + */ + Song *GetOrder(unsigned _order) const { + return Get(OrderToPosition(_order)); + } + + /** + * Is the song at the specified position newer than the specified + * version? + */ + bool IsNewerAtPosition(unsigned position, uint32_t _version) const { + assert(position < length); + + return _version > version || + items[position].version >= _version || + items[position].version == 0; + } + + /** + * Returns the order number following the specified one. This takes + * end of queue and "repeat" mode into account. + * + * @return the next order number, or -1 to stop playback + */ + gcc_pure + int GetNextOrder(unsigned order) const; + + /** + * Increments the queue's version number. This handles integer + * overflow well. + */ + void IncrementVersion(); + + /** + * Marks the specified song as "modified" and increments the version + * number. + */ + void ModifyAtOrder(unsigned order); + + /** + * Marks all songs as "modified" and increments the version number. + */ + void ModifyAll(); + + /** + * Appends a song to the queue and returns its position. Prior to + * that, the caller must check if the queue is already full. + * + * If a song is not in the database (determined by + * Song::IsInDatabase()), it is freed when removed from the + * queue. + * + * @param priority the priority of this new queue item + */ + unsigned Append(Song *song, uint8_t priority); + + /** + * Swaps two songs, addressed by their position. + */ + void SwapPositions(unsigned position1, unsigned position2); + + /** + * Swaps two songs, addressed by their order number. + */ + void SwapOrders(unsigned order1, unsigned order2) { + std::swap(order[order1], order[order2]); + } + + /** + * Moves a song to a new position. + */ + void MovePostion(unsigned from, unsigned to); + + /** + * Moves a range of songs to a new position. + */ + void MoveRange(unsigned start, unsigned end, unsigned to); + + /** + * Removes a song from the playlist. + */ + void DeletePosition(unsigned position); + + /** + * Removes all songs from the playlist. + */ + void Clear(); + + /** + * Initializes the "order" array, and restores "normal" order. + */ + void RestoreOrder() { + for (unsigned i = 0; i < length; ++i) + order[i] = i; + } + + /** + * Shuffle the order of items in the specified range, ignoring + * their priorities. + */ + void ShuffleOrderRange(unsigned start, unsigned end); + + /** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ + void ShuffleOrderRangeWithPriority(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of songs, but does not move them + * physically. This is used in random mode. + */ + void ShuffleOrder(); + + void ShuffleOrderFirst(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of the last song in the specified + * (order) range. This is used in random mode after a song has been + * appended by queue_append(). + */ + void ShuffleOrderLast(unsigned start, unsigned end); + + /** + * Shuffles a (position) range in the queue. The songs are physically + * shuffled, not by using the "order" mapping. + */ + void ShuffleRange(unsigned start, unsigned end); + + bool SetPriority(unsigned position, uint8_t priority, int after_order); + + bool SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + +private: + /** + * Moves a song to a new position in the "order" list. + */ + void MoveOrder(unsigned from_order, unsigned to_order); + + void MoveItemTo(unsigned from, unsigned to) { + unsigned from_id = items[from].id; + + items[to] = items[from]; + items[to].version = version; + id_table.Move(from_id, to); + } + + /** + * Find the first item that has this specified priority or + * higher. + */ + gcc_pure + unsigned FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const; + + gcc_pure + unsigned CountSamePriority(unsigned start_order, + uint8_t priority) const; +}; + +#endif diff --git a/src/QueueCommands.cxx b/src/QueueCommands.cxx new file mode 100644 index 000000000..a70a5f250 --- /dev/null +++ b/src/QueueCommands.cxx @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "QueueCommands.hxx" +#include "CommandError.hxx" +#include "DatabaseQueue.hxx" +#include "SongFilter.hxx" +#include "DatabaseSelection.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "ClientFile.hxx" +#include "ClientInternal.hxx" +#include "Partition.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/Path.hxx" + +#include <string.h> + +enum command_return +handle_add(Client *client, gcc_unused int argc, char *argv[]) +{ + char *uri = argv[1]; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path_utf8 = uri + 7; + const Path path_fs = Path::FromUTF8(path_utf8); + + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return COMMAND_RETURN_ERROR; + } + + Error error; + if (!client_allow_file(client, path_fs, error)) + return print_error(client, error); + + result = client->partition.AppendFile(path_utf8); + return print_playlist_result(client, result); + } + + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = client->partition.AppendURI(uri); + return print_playlist_result(client, result); + } + + const DatabaseSelection selection(uri, true); + Error error; + return AddFromDatabase(client->partition, selection, error) + ? COMMAND_RETURN_OK + : print_error(client, error); +} + +enum command_return +handle_addid(Client *client, int argc, char *argv[]) +{ + char *uri = argv[1]; + unsigned added_id; + enum playlist_result result; + + if (strncmp(uri, "file:///", 8) == 0) { + const char *path_utf8 = uri + 7; + const Path path_fs = Path::FromUTF8(path_utf8); + + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return COMMAND_RETURN_ERROR; + } + + Error error; + if (!client_allow_file(client, path_fs, error)) + return print_error(client, error); + + result = client->partition.AppendFile(path_utf8, &added_id); + } else { + if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return COMMAND_RETURN_ERROR; + } + + result = client->partition.AppendURI(uri, &added_id); + } + + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + + if (argc == 3) { + unsigned to; + if (!check_unsigned(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + result = client->partition.MoveId(added_id, to); + if (result != PLAYLIST_RESULT_SUCCESS) { + enum command_return ret = + print_playlist_result(client, result); + client->partition.DeleteId(added_id); + return ret; + } + } + + client_printf(client, "Id: %u\n", added_id); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_delete(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned start, end; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.DeleteRange(start, end); + return print_playlist_result(client, result); +} + +enum command_return +handle_deleteid(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned id; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.DeleteId(id); + return print_playlist_result(client, result); +} + +enum command_return +handle_playlist(Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + playlist_print_uris(client, &client->playlist); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_shuffle(gcc_unused Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + unsigned start = 0, end = client->playlist.queue.GetLength(); + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + client->partition.Shuffle(start, end); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_clear(gcc_unused Client *client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client->partition.ClearQueue(); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchanges(Client *client, gcc_unused int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_info(client, &client->playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_plchangesposid(Client *client, gcc_unused int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return COMMAND_RETURN_ERROR; + + playlist_print_changes_position(client, &client->playlist, version); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]) +{ + unsigned start = 0, end = G_MAXUINT; + bool ret; + + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + + ret = playlist_print_info(client, &client->playlist, start, end); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_BAD_RANGE); + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]) +{ + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + + bool ret = playlist_print_id(client, &client->playlist, id); + if (!ret) + return print_playlist_result(client, + PLAYLIST_RESULT_NO_SUCH_SONG); + } else { + playlist_print_info(client, &client->playlist, 0, G_MAXUINT); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_playlist_match(Client *client, int argc, char *argv[], + bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + playlist_print_find(client, &client->playlist, filter); + return COMMAND_RETURN_OK; +} + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, false); +} + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, true); +} + +enum command_return +handle_prio(Client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SetPriorityRange(start_position, + end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SetPriorityId(song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +enum command_return +handle_move(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned start, end; + int to; + + if (!check_range(client, &start, &end, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.MoveRange(start, end, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_moveid(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned id; + int to; + + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_int(client, &to, argv[2])) + return COMMAND_RETURN_ERROR; + enum playlist_result result = client->partition.MoveId(id, to); + return print_playlist_result(client, result); +} + +enum command_return +handle_swap(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned song1, song2; + + if (!check_unsigned(client, &song1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &song2, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + client->partition.SwapPositions(song1, song2); + return print_playlist_result(client, result); +} + +enum command_return +handle_swapid(Client *client, gcc_unused int argc, char *argv[]) +{ + unsigned id1, id2; + + if (!check_unsigned(client, &id1, argv[1])) + return COMMAND_RETURN_ERROR; + if (!check_unsigned(client, &id2, argv[2])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = client->partition.SwapIds(id1, id2); + return print_playlist_result(client, result); +} diff --git a/src/QueueCommands.hxx b/src/QueueCommands.hxx new file mode 100644 index 000000000..97b61e212 --- /dev/null +++ b/src/QueueCommands.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_QUEUE_COMMANDS_HXX +#define MPD_QUEUE_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_add(Client *client, int argc, char *argv[]); + +enum command_return +handle_addid(Client *client, int argc, char *argv[]); + +enum command_return +handle_delete(Client *client, int argc, char *argv[]); + +enum command_return +handle_deleteid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlist(Client *client, int argc, char *argv[]); + +enum command_return +handle_shuffle(Client *client, int argc, char *argv[]); + +enum command_return +handle_clear(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchanges(Client *client, int argc, char *argv[]); + +enum command_return +handle_plchangesposid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistinfo(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistid(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistfind(Client *client, int argc, char *argv[]); + +enum command_return +handle_playlistsearch(Client *client, int argc, char *argv[]); + +enum command_return +handle_prio(Client *client, int argc, char *argv[]); + +enum command_return +handle_prioid(Client *client, int argc, char *argv[]); + +enum command_return +handle_move(Client *client, int argc, char *argv[]); + +enum command_return +handle_moveid(Client *client, int argc, char *argv[]); + +enum command_return +handle_swap(Client *client, int argc, char *argv[]); + +enum command_return +handle_swapid(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx new file mode 100644 index 000000000..d8d94d3b0 --- /dev/null +++ b/src/QueuePrint.cxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "QueuePrint.hxx" +#include "Queue.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" + +extern "C" { +#include "Song.hxx" +} + +/** + * Send detailed information about a range of songs in the queue to a + * client. + * + * @param client the client which has requested information + * @param start the index of the first song (including) + * @param end the index of the last song (excluding) + */ +static void +queue_print_song_info(Client *client, const struct queue *queue, + unsigned position) +{ + song_print_info(client, queue->Get(position)); + client_printf(client, "Pos: %u\nId: %u\n", + position, queue->PositionToId(position)); + + uint8_t priority = queue->GetPriorityAtPosition(position); + if (priority != 0) + client_printf(client, "Prio: %u\n", priority); +} + +void +queue_print_info(Client *client, const struct queue *queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->GetLength()); + + for (unsigned i = start; i < end; ++i) + queue_print_song_info(client, queue, i); +} + +void +queue_print_uris(Client *client, const struct queue *queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->GetLength()); + + for (unsigned i = start; i < end; ++i) { + client_printf(client, "%i:", i); + song_print_uri(client, queue->Get(i)); + } +} + +void +queue_print_changes_info(Client *client, const struct queue *queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + if (queue->IsNewerAtPosition(i, version)) + queue_print_song_info(client, queue, i); + } +} + +void +queue_print_changes_position(Client *client, const struct queue *queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) + if (queue->IsNewerAtPosition(i, version)) + client_printf(client, "cpos: %i\nId: %i\n", + i, queue->PositionToId(i)); +} + +void +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + const Song *song = queue->Get(i); + + if (filter.Match(*song)) + queue_print_song_info(client, queue, i); + } +} diff --git a/src/QueuePrint.hxx b/src/QueuePrint.hxx new file mode 100644 index 000000000..6b3a29fb6 --- /dev/null +++ b/src/QueuePrint.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This library sends information about songs in the queue to the + * client. + */ + +#ifndef MPD_QUEUE_PRINT_HXX +#define MPD_QUEUE_PRINT_HXX + +#include <stdint.h> + +struct queue; +class SongFilter; +class Client; + +void +queue_print_info(Client *client, const struct queue *queue, + unsigned start, unsigned end); + +void +queue_print_uris(Client *client, const struct queue *queue, + unsigned start, unsigned end); + +void +queue_print_changes_info(Client *client, const struct queue *queue, + uint32_t version); + +void +queue_print_changes_position(Client *client, const struct queue *queue, + uint32_t version); + +void +queue_find(Client *client, const struct queue *queue, + const SongFilter &filter); + +#endif diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx new file mode 100644 index 000000000..8f22e312f --- /dev/null +++ b/src/QueueSave.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "QueueSave.hxx" +#include "Queue.hxx" +#include "PlaylistError.hxx" +#include "Song.hxx" +#include "SongSave.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "TextFile.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <stdlib.h> + +#define PRIO_LABEL "Prio: " + +static void +queue_save_database_song(FILE *fp, int idx, const Song *song) +{ + char *uri = song->GetURI(); + + fprintf(fp, "%i:%s\n", idx, uri); + g_free(uri); +} + +static void +queue_save_full_song(FILE *fp, const Song *song) +{ + song_save(fp, song); +} + +static void +queue_save_song(FILE *fp, int idx, const Song *song) +{ + if (song->IsInDatabase()) + queue_save_database_song(fp, idx, song); + else + queue_save_full_song(fp, song); +} + +void +queue_save(FILE *fp, const struct queue *queue) +{ + for (unsigned i = 0; i < queue->GetLength(); i++) { + uint8_t prio = queue->GetPriorityAtPosition(i); + if (prio != 0) + fprintf(fp, PRIO_LABEL "%u\n", prio); + + queue_save_song(fp, i, queue->Get(i)); + } +} + +void +queue_load_song(TextFile &file, const char *line, queue *queue) +{ + if (queue->IsFull()) + return; + + uint8_t priority = 0; + if (g_str_has_prefix(line, PRIO_LABEL)) { + priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); + + line = file.ReadLine(); + if (line == NULL) + return; + } + + const Database *db = nullptr; + Song *song; + + 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; + + Error error; + song = song_load(file, NULL, uri, error); + if (song == NULL) { + LogError(error); + return; + } + } else { + char *endptr; + long ret = strtol(line, &endptr, 10); + if (ret < 0 || *endptr != ':' || endptr[1] == 0) { + LogError(playlist_domain, + "Malformed playlist line in state file"); + return; + } + + const char *uri = endptr + 1; + + if (uri_has_scheme(uri)) { + song = Song::NewRemote(uri); + } else { + db = GetDatabase(IgnoreError()); + if (db == nullptr) + return; + + song = db->GetSong(uri, IgnoreError()); + if (song == nullptr) + return; + } + } + + queue->Append(song, priority); + + if (db != nullptr) + db->ReturnSong(song); +} diff --git a/src/QueueSave.hxx b/src/QueueSave.hxx new file mode 100644 index 000000000..25d7804fb --- /dev/null +++ b/src/QueueSave.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This library saves the queue into the state file, and also loads it + * back into memory. + */ + +#ifndef MPD_QUEUE_SAVE_HXX +#define MPD_QUEUE_SAVE_HXX + +#include <stdio.h> + +struct queue; +class TextFile; + +void +queue_save(FILE *fp, const struct queue *queue); + +/** + * Loads one song from the state file and appends it to the queue. + */ +void +queue_load_song(TextFile &file, const char *line, queue *queue); + +#endif diff --git a/src/ReplayGainConfig.cxx b/src/ReplayGainConfig.cxx new file mode 100644 index 000000000..9665c8fcf --- /dev/null +++ b/src/ReplayGainConfig.cxx @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ReplayGainConfig.hxx" +#include "Idle.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "Playlist.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; + +static constexpr bool DEFAULT_REPLAYGAIN_LIMIT = true; + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit = DEFAULT_REPLAYGAIN_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"; + } + + assert(false); + gcc_unreachable(); +} + +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)) { + FormatFatalError("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') { + FormatFatalError("Replaygain preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + FormatFatalError("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') { + FormatFatalError("Replaygain missing preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + FormatFatalError("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(bool random_mode) +{ + enum replay_gain_mode rgm; + + rgm = replay_gain_mode; + + if (rgm == REPLAY_GAIN_AUTO) + rgm = random_mode ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; + + return rgm; +} diff --git a/src/ReplayGainConfig.hxx b/src/ReplayGainConfig.hxx new file mode 100644 index 000000000..5b81d1cc1 --- /dev/null +++ b/src/ReplayGainConfig.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_HXX +#define MPD_REPLAY_GAIN_CONFIG_HXX + +#include "check.h" +#include "ReplayGainInfo.hxx" + +extern enum replay_gain_mode replay_gain_mode; +extern float replay_gain_preamp; +extern float replay_gain_missing_preamp; +extern bool replay_gain_limit; + +void replay_gain_global_init(void); + +/** + * 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(bool random_mode); + +#endif diff --git a/src/ReplayGainInfo.cxx b/src/ReplayGainInfo.cxx new file mode 100644 index 000000000..ac1c13822 --- /dev/null +++ b/src/ReplayGainInfo.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ReplayGainInfo.hxx" + +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/ReplayGainInfo.hxx b/src/ReplayGainInfo.hxx new file mode 100644 index 000000000..bfc5f68a7 --- /dev/null +++ b/src/ReplayGainInfo.hxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_REPLAY_GAIN_INFO_HXX + +#include "check.h" + +#include <cmath> + +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 !std::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/SignalHandlers.cxx b/src/SignalHandlers.cxx new file mode 100644 index 000000000..c884b97fd --- /dev/null +++ b/src/SignalHandlers.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SignalHandlers.hxx" +#include "event/SignalMonitor.hxx" + +#ifndef WIN32 + +#include "Log.hxx" +#include "LogInit.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "GlobalEvents.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" + +#include <signal.h> + +static constexpr Domain signal_handlers_domain("signal_handlers"); + +static EventLoop *shutdown_loop; + +static void +HandleShutdownSignal() +{ + shutdown_loop->Break(); +} + +static void +x_sigaction(int signum, const struct sigaction *act) +{ + if (sigaction(signum, act, NULL) < 0) + FatalSystemError("sigaction() failed"); +} + +static void +handle_reload_event(void) +{ + LogDebug(signal_handlers_domain, "got SIGHUP, reopening log files"); + cycle_log_files(); +} + +#endif + +void +SignalHandlersInit(EventLoop &loop) +{ + SignalMonitorInit(loop); + +#ifndef WIN32 + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + x_sigaction(SIGPIPE, &sa); + + shutdown_loop = &loop; + SignalMonitorRegister(SIGINT, HandleShutdownSignal); + SignalMonitorRegister(SIGTERM, HandleShutdownSignal); + + SignalMonitorRegister(SIGHUP, handle_reload_event); +#endif +} + +void +SignalHandlersFinish() +{ + SignalMonitorFinish(); +} diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx new file mode 100644 index 000000000..90dab6dec --- /dev/null +++ b/src/SignalHandlers.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SIGNAL_HANDLERS_HXX +#define MPD_SIGNAL_HANDLERS_HXX + +class EventLoop; + +void +SignalHandlersInit(EventLoop &loop); + +void +SignalHandlersFinish(); + +#endif diff --git a/src/Song.cxx b/src/Song.cxx new file mode 100644 index 000000000..5ee4c5545 --- /dev/null +++ b/src/Song.cxx @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Song.hxx" +#include "Directory.hxx" +#include "tag/Tag.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +Directory detached_root; + +static Song * +song_alloc(const char *uri, Directory *parent) +{ + size_t uri_length; + + assert(uri); + uri_length = strlen(uri); + assert(uri_length); + + Song *song = (Song *) + g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); + + song->tag = nullptr; + memcpy(song->uri, uri, uri_length + 1); + song->parent = parent; + song->mtime = 0; + song->start_ms = song->end_ms = 0; + + return song; +} + +Song * +Song::NewRemote(const char *uri) +{ + return song_alloc(uri, nullptr); +} + +Song * +Song::NewFile(const char *path, Directory *parent) +{ + assert((parent == nullptr) == (*path == '/')); + + return song_alloc(path, parent); +} + +Song * +Song::ReplaceURI(const char *new_uri) +{ + Song *new_song = song_alloc(new_uri, parent); + new_song->tag = tag; + new_song->mtime = mtime; + new_song->start_ms = start_ms; + new_song->end_ms = end_ms; + g_free(this); + return new_song; +} + +Song * +Song::NewDetached(const char *uri) +{ + assert(uri != nullptr); + + return song_alloc(uri, &detached_root); +} + +Song * +Song::DupDetached() const +{ + Song *song; + if (IsInDatabase()) { + char *new_uri = GetURI(); + song = NewDetached(new_uri); + g_free(new_uri); + } else + song = song_alloc(uri, nullptr); + + song->tag = tag != nullptr ? new Tag(*tag) : nullptr; + song->mtime = mtime; + song->start_ms = start_ms; + song->end_ms = end_ms; + + return song; +} + +void +Song::Free() +{ + delete tag; + g_free(this); +} + +gcc_pure +static inline bool +directory_equals(const Directory &a, const Directory &b) +{ + return strcmp(a.path, b.path) == 0; +} + +gcc_pure +static inline bool +directory_is_same(const Directory *a, const Directory *b) +{ + return a == b || + (a != nullptr && b != nullptr && + directory_equals(*a, *b)); + +} + +bool +song_equals(const Song *a, const Song *b) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a->parent != nullptr && b->parent != nullptr && + !directory_equals(*a->parent, *b->parent) && + (a->parent == &detached_root || b->parent == &detached_root)) { + /* must compare the full URI if one of the objects is + "detached" */ + char *au = a->GetURI(); + char *bu = b->GetURI(); + const bool result = strcmp(au, bu) == 0; + g_free(bu); + g_free(au); + return result; + } + + return directory_is_same(a->parent, b->parent) && + strcmp(a->uri, b->uri) == 0; +} + +char * +Song::GetURI() const +{ + assert(*uri); + + if (!IsInDatabase() || parent->IsRoot()) + return g_strdup(uri); + else + return g_strconcat(parent->GetPath(), + "/", uri, nullptr); +} + +double +Song::GetDuration() const +{ + if (end_ms > 0) + return (end_ms - start_ms) / 1000.0; + + if (tag == nullptr) + return 0; + + return tag->time - start_ms / 1000.0; +} diff --git a/src/Song.hxx b/src/Song.hxx new file mode 100644 index 000000000..c1122f43b --- /dev/null +++ b/src/Song.hxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_HXX +#define MPD_SONG_HXX + +#include "util/list.h" +#include "gcc.h" + +#include <assert.h> +#include <sys/time.h> + +#define SONG_FILE "file: " +#define SONG_TIME "Time: " + +struct Tag; + +/** + * A dummy #directory instance that is used for "detached" song + * copies. + */ +extern struct Directory detached_root; + +struct Song { + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) if this song is + * not in the database. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head siblings; + + Tag *tag; + Directory *parent; + time_t mtime; + + /** + * 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 */ + gcc_malloc + static Song *NewRemote(const char *uri); + + /** allocate a new song with a local file name */ + gcc_malloc + static Song *NewFile(const char *path_utf8, Directory *parent); + + /** + * allocate a new song structure with a local file name and attempt to + * load its metadata. If all decoder plugin fail to read its meta + * data, nullptr is returned. + */ + gcc_malloc + static Song *LoadFile(const char *path_utf8, Directory *parent); + + /** + * Replaces the URI of a song object. The given song object + * is destroyed, and a newly allocated one is returned. It + * does not update the reference within the parent directory; + * the caller is responsible for doing that. + */ + gcc_malloc + Song *ReplaceURI(const char *uri); + + /** + * Creates a "detached" song object. + */ + gcc_malloc + static Song *NewDetached(const char *uri); + + /** + * Creates a duplicate of the song object. If the object is + * in the database, it creates a "detached" copy of this song, + * see Song::IsDetached(). + */ + gcc_malloc + Song *DupDetached() const; + + void Free(); + + bool IsInDatabase() const { + return parent != nullptr; + } + + bool IsFile() const { + return IsInDatabase() || uri[0] == '/'; + } + + bool IsDetached() const { + assert(IsInDatabase()); + + return parent == &detached_root; + } + + bool UpdateFile(); + bool UpdateFileInArchive(); + + /** + * Returns the URI of the song in UTF-8 encoding, including its + * location within the music directory. + * + * The return value is allocated on the heap, and must be freed by the + * caller. + */ + gcc_malloc + char *GetURI() const; + + gcc_pure + double GetDuration() const; +}; + +/** + * Returns true if both objects refer to the same physical song. + */ +gcc_pure +bool +song_equals(const Song *a, const Song *b); + +#endif diff --git a/src/SongEnumerator.hxx b/src/SongEnumerator.hxx new file mode 100644 index 000000000..0e268a31a --- /dev/null +++ b/src/SongEnumerator.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_ENUMERATOR_HXX +#define MPD_SONG_ENUMERATOR_HXX + +struct Song; + +/** + * An object which provides serial access to a number of #Song + * objects. It is used to enumerate the contents of a playlist file. + */ +class SongEnumerator { +public: + virtual ~SongEnumerator() {} + + /** + * Obtain the next song. The caller is responsible for + * freeing the returned #Song object. Returns nullptr if + * there are no more songs. + */ + virtual Song *NextSong() = 0; +}; + +#endif diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx new file mode 100644 index 000000000..4b8ae20ba --- /dev/null +++ b/src/SongFilter.cxx @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SongFilter.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#define LOCATE_TAG_FILE_KEY "file" +#define LOCATE_TAG_FILE_KEY_OLD "filename" +#define LOCATE_TAG_ANY_KEY "any" + +unsigned +locate_parse_type(const char *str) +{ + if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || + 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) + return LOCATE_TAG_FILE_TYPE; + + if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) + return LOCATE_TAG_ANY_TYPE; + + return tag_name_parse_i(str); +} + +SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case) + :tag(_tag), fold_case(_fold_case), + value(fold_case + ? g_utf8_casefold(_value, -1) + : g_strdup(_value)) +{ +} + +SongFilter::Item::~Item() +{ + g_free(value); +} + +bool +SongFilter::Item::StringMatch(const char *s) const +{ + assert(value != nullptr); + assert(s != nullptr); + + if (fold_case) { + char *p = g_utf8_casefold(s, -1); + const bool result = strstr(p, value) != NULL; + g_free(p); + return result; + } else { + return strcmp(s, value) == 0; + } +} + +bool +SongFilter::Item::Match(const TagItem &item) const +{ + return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) && + StringMatch(item.value); +} + +bool +SongFilter::Item::Match(const Tag &_tag) const +{ + bool visited_types[TAG_NUM_OF_ITEM_TYPES]; + std::fill_n(visited_types, TAG_NUM_OF_ITEM_TYPES, false); + + for (unsigned i = 0; i < _tag.num_items; i++) { + visited_types[_tag.items[i]->type] = true; + + if (Match(*_tag.items[i])) + return true; + } + + if (tag < TAG_NUM_OF_ITEM_TYPES && !visited_types[tag]) { + /* If the search critieron was not visited during the + sweep through the song's tag, it means this field + is absent from the tag or empty. Thus, if the + searched string is also empty (first char is a \0), + then it's a match as well and we should return + true. */ + if (*value == 0) + return true; + + if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) { + /* if we're looking for "album artist", but + only "artist" exists, use that */ + for (unsigned i = 0; i < _tag.num_items; i++) { + const TagItem &item = *_tag.items[i]; + if (item.type == TAG_ARTIST && + StringMatch(item.value)) + return true; + } + } + } + + return false; +} + +bool +SongFilter::Item::Match(const Song &song) const +{ + if (tag == LOCATE_TAG_FILE_TYPE || tag == LOCATE_TAG_ANY_TYPE) { + char *uri = song.GetURI(); + const bool result = StringMatch(uri); + g_free(uri); + + if (result || tag == LOCATE_TAG_FILE_TYPE) + return result; + } + + return song.tag != NULL && Match(*song.tag); +} + +SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) +{ + items.push_back(Item(tag, value, fold_case)); +} + +SongFilter::~SongFilter() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +SongFilter::Parse(const char *tag_string, const char *value, bool fold_case) +{ + unsigned tag = locate_parse_type(tag_string); + if (tag == TAG_NUM_OF_ITEM_TYPES) + return false; + + items.push_back(Item(tag, value, fold_case)); + return true; +} + +bool +SongFilter::Parse(unsigned argc, char *argv[], bool fold_case) +{ + if (argc == 0 || argc % 2 != 0) + return false; + + for (unsigned i = 0; i < argc; i += 2) + if (!Parse(argv[i], argv[i + 1], fold_case)) + return false; + + return true; +} + +bool +SongFilter::Match(const Song &song) const +{ + for (const auto &i : items) + if (!i.Match(song)) + return false; + + return true; +} diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx new file mode 100644 index 000000000..88378d710 --- /dev/null +++ b/src/SongFilter.hxx @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_FILTER_HXX +#define MPD_SONG_FILTER_HXX + +#include "gcc.h" + +#include <list> + +#include <stdint.h> + +#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 +#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 + +struct Tag; +struct TagItem; +struct Song; + +class SongFilter { + class Item { + uint8_t tag; + + bool fold_case; + + char *value; + + public: + gcc_nonnull(3) + Item(unsigned tag, const char *value, bool fold_case=false); + + Item(const Item &other) = delete; + + Item(Item &&other) + :tag(other.tag), fold_case(other.fold_case), + value(other.value) { + other.value = nullptr; + } + + ~Item(); + + Item &operator=(const Item &other) = delete; + + unsigned GetTag() const { + return tag; + } + + gcc_pure gcc_nonnull(2) + bool StringMatch(const char *s) const; + + gcc_pure + bool Match(const TagItem &tag_item) const; + + gcc_pure + bool Match(const Tag &tag) const; + + gcc_pure + bool Match(const Song &song) const; + }; + + std::list<Item> items; + +public: + SongFilter() = default; + + gcc_nonnull(3) + SongFilter(unsigned tag, const char *value, bool fold_case=false); + + ~SongFilter(); + + gcc_nonnull(2,3) + bool Parse(const char *tag, const char *value, bool fold_case=false); + + gcc_nonnull(3) + bool Parse(unsigned argc, char *argv[], bool fold_case=false); + + gcc_pure + bool Match(const Tag &tag) const; + + gcc_pure + bool Match(const Song &song) const; +}; + +/** + * @return #TAG_NUM_OF_ITEM_TYPES on error + */ +gcc_pure +unsigned +locate_parse_type(const char *str); + +#endif diff --git a/src/SongPointer.hxx b/src/SongPointer.hxx new file mode 100644 index 000000000..ded3b3e1d --- /dev/null +++ b/src/SongPointer.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_POINTER_HXX +#define MPD_SONG_POINTER_HXX + +#include "Song.hxx" + +#include <utility> + +class SongPointer { + Song *song; + +public: + explicit SongPointer(Song *_song) + :song(_song) {} + + SongPointer(const SongPointer &) = delete; + + SongPointer(SongPointer &&other):song(other.song) { + other.song = nullptr; + } + + ~SongPointer() { + if (song != nullptr) + song->Free(); + } + + SongPointer &operator=(const SongPointer &) = delete; + + SongPointer &operator=(SongPointer &&other) { + std::swap(song, other.song); + return *this; + } + + operator const Song *() const { + return song; + } + + Song *Steal() { + auto result = song; + song = nullptr; + return result; + } +}; + +#endif diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx new file mode 100644 index 000000000..65d27ca77 --- /dev/null +++ b/src/SongPrint.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SongPrint.hxx" +#include "Song.hxx" +#include "Directory.hxx" +#include "TimePrint.hxx" +#include "TagPrint.hxx" +#include "Mapper.hxx" +#include "Client.hxx" +#include "util/UriUtil.hxx" + +#include <glib.h> + +void +song_print_uri(Client *client, Song *song) +{ + if (song->IsInDatabase() && !song->parent->IsRoot()) { + client_printf(client, "%s%s/%s\n", SONG_FILE, + song->parent->GetPath(), song->uri); + } else { + char *allocated; + const char *uri; + + uri = allocated = uri_remove_auth(song->uri); + if (uri == NULL) + uri = song->uri; + + client_printf(client, "%s%s\n", SONG_FILE, + map_to_relative_path(uri)); + + g_free(allocated); + } +} + +void +song_print_info(Client *client, Song *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) + time_print(client, "Last-Modified", song->mtime); + + if (song->tag != nullptr) + tag_print(client, *song->tag); +} diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx new file mode 100644 index 000000000..a82b54cfe --- /dev/null +++ b/src/SongPrint.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_PRINT_HXX +#define MPD_SONG_PRINT_HXX + +struct Song; +class Client; + +void +song_print_info(Client *client, Song *song); + +void +song_print_uri(Client *client, Song *song); + +#endif diff --git a/src/SongSave.cxx b/src/SongSave.cxx new file mode 100644 index 000000000..8005885eb --- /dev/null +++ b/src/SongSave.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SongSave.hxx" +#include "Song.hxx" +#include "TagSave.hxx" +#include "Directory.hxx" +#include "TextFile.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <string.h> + +#define SONG_MTIME "mtime" +#define SONG_END "song_end" + +static constexpr Domain song_save_domain("song_save"); + +void +song_save(FILE *fp, const Song *song) +{ + fprintf(fp, SONG_BEGIN "%s\n", song->uri); + + 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 != nullptr) + tag_save(fp, *song->tag); + + fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime); + fprintf(fp, SONG_END "\n"); +} + +Song * +song_load(TextFile &file, Directory *parent, const char *uri, + Error &error) +{ + Song *song = parent != NULL + ? Song::NewFile(uri, parent) + : Song::NewRemote(uri); + char *line, *colon; + enum tag_type type; + const char *value; + + TagBuilder tag; + + while ((line = file.ReadLine()) != NULL && + strcmp(line, SONG_END) != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + song->Free(); + + error.Format(song_save_domain, + "unknown line in db: %s", line); + return NULL; + } + + *colon++ = 0; + value = strchug_fast_c(colon); + + if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { + tag.AddItem(type, value); + } else if (strcmp(line, "Time") == 0) { + tag.SetTime(atoi(value)); + } else if (strcmp(line, "Playlist") == 0) { + tag.SetHasPlaylist(strcmp(value, "yes") == 0); + } 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 { + song->Free(); + + error.Format(song_save_domain, + "unknown line in db: %s", line); + return NULL; + } + } + + if (tag.IsDefined()) + song->tag = tag.Commit(); + + return song; +} diff --git a/src/SongSave.hxx b/src/SongSave.hxx new file mode 100644 index 000000000..f18aa9660 --- /dev/null +++ b/src/SongSave.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_SAVE_HXX +#define MPD_SONG_SAVE_HXX + +#include <stdio.h> + +#define SONG_BEGIN "song_begin: " + +struct Song; +struct Directory; +class TextFile; +class Error; + +void +song_save(FILE *fp, const Song *song); + +/** + * Loads a song from the input file. Reading stops after the + * "song_end" line. + * + * @param error location to store the error occurring + * @return true on success, false on error + */ +Song * +song_load(TextFile &file, Directory *parent, const char *uri, + Error &error); + +#endif diff --git a/src/SongSort.cxx b/src/SongSort.cxx new file mode 100644 index 000000000..f1eba0439 --- /dev/null +++ b/src/SongSort.cxx @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SongSort.hxx" +#include "Song.hxx" +#include "util/list.h" +#include "tag/Tag.hxx" + +extern "C" { +#include "util/list_sort.h" +} + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +static const char * +tag_get_value_checked(const Tag *tag, enum tag_type type) +{ + return tag != NULL + ? tag->GetValue(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 Tag *a, const 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. + */ +static int +compare_number_string(const char *a, const char *b) +{ + long ai = a == NULL ? 0 : strtol(a, NULL, 10); + long bi = b == NULL ? 0 : strtol(b, NULL, 10); + + if (ai <= 0) + return bi <= 0 ? 0 : -1; + + if (bi <= 0) + return 1; + + return ai - bi; +} + +static int +compare_tag_item(const Tag *a, const Tag *b, enum tag_type 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 */ +static int +song_cmp(gcc_unused void *priv, struct list_head *_a, struct list_head *_b) +{ + const Song *a = (const Song *)_a; + const Song *b = (const Song *)_b; + int ret; + + /* 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_TRACK); + if (ret != 0) + return ret; + + /* still no difference? compare file name */ + return g_utf8_collate(a->uri, b->uri); +} + +void +song_list_sort(struct list_head *songs) +{ + list_sort(NULL, songs, song_cmp); +} diff --git a/src/SongSort.hxx b/src/SongSort.hxx new file mode 100644 index 000000000..b3b67b0c0 --- /dev/null +++ b/src/SongSort.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_SORT_HXX +#define MPD_SONG_SORT_HXX + +struct list_head; + +void +song_list_sort(struct list_head *songs); + +#endif diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx new file mode 100644 index 000000000..589bc2a4a --- /dev/null +++ b/src/SongSticker.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SongSticker.hxx" +#include "StickerDatabase.hxx" +#include "Song.hxx" +#include "Directory.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +char * +sticker_song_get_value(const Song *song, const char *name) +{ + assert(song != NULL); + assert(song->IsInDatabase()); + + char *uri = song->GetURI(); + char *value = sticker_load_value("song", uri, name); + g_free(uri); + + return value; +} + +bool +sticker_song_set_value(const Song *song, + const char *name, const char *value) +{ + assert(song != NULL); + assert(song->IsInDatabase()); + + char *uri = song->GetURI(); + bool ret = sticker_store_value("song", uri, name, value); + g_free(uri); + + return ret; +} + +bool +sticker_song_delete(const Song *song) +{ + assert(song != NULL); + assert(song->IsInDatabase()); + + char *uri = song->GetURI(); + bool ret = sticker_delete("song", uri); + g_free(uri); + + return ret; +} + +bool +sticker_song_delete_value(const Song *song, const char *name) +{ + assert(song != NULL); + assert(song->IsInDatabase()); + + char *uri = song->GetURI(); + bool success = sticker_delete_value("song", uri, name); + g_free(uri); + + return success; +} + +struct sticker * +sticker_song_get(const Song *song) +{ + assert(song != NULL); + assert(song->IsInDatabase()); + + char *uri = song->GetURI(); + struct sticker *sticker = sticker_load("song", uri); + g_free(uri); + + return sticker; +} + +struct sticker_song_find_data { + Directory *directory; + const char *base_uri; + size_t base_uri_length; + + void (*func)(Song *song, const char *value, + void *user_data); + void *user_data; +}; + +static void +sticker_song_find_cb(const char *uri, const char *value, void *user_data) +{ + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; + + if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) + /* should not happen, ignore silently */ + return; + + Song *song = data->directory->LookupSong(uri + data->base_uri_length); + if (song != NULL) + data->func(song, value, data->user_data); +} + +bool +sticker_song_find(Directory *directory, const char *name, + void (*func)(Song *song, const char *value, + void *user_data), + void *user_data) +{ + struct sticker_song_find_data data; + data.directory = directory; + data.func = func; + data.user_data = user_data; + + char *allocated; + data.base_uri = directory->GetPath(); + if (*data.base_uri != 0) + /* append slash to base_uri */ + data.base_uri = allocated = + g_strconcat(data.base_uri, "/", NULL); + else + /* searching in root directory - no trailing slash */ + allocated = NULL; + + data.base_uri_length = strlen(data.base_uri); + + bool success = sticker_find("song", data.base_uri, name, + sticker_song_find_cb, &data); + g_free(allocated); + + return success; +} diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx new file mode 100644 index 000000000..0f3e0bf41 --- /dev/null +++ b/src/SongSticker.hxx @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_STICKER_HXX +#define MPD_SONG_STICKER_HXX + +struct Song; +struct Directory; +struct sticker; + +/** + * Returns one value from a song's sticker record. The caller must + * free the return value with g_free(). + */ +char * +sticker_song_get_value(const Song *song, const char *name); + +/** + * Sets a sticker value in the specified song. Overwrites existing + * values. + */ +bool +sticker_song_set_value(const Song *song, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All values are deleted. + */ +bool +sticker_song_delete(const Song *song); + +/** + * Deletes a sticker value. Does nothing if the sticker did not + * exist. + */ +bool +sticker_song_delete_value(const Song *song, const char *name); + +/** + * Loads the sticker for the specified song. + * + * @param song the song object + * @return a sticker object, or NULL on error or if there is no sticker + */ +struct sticker * +sticker_song_get(const Song *song); + +/** + * Finds stickers with the specified name below the specified + * directory. + * + * Caller must lock the #db_mutex. + * + * @param directory the base directory to search in + * @param name the name of the sticker + * @return true on success (even if no sticker was found), false on + * failure + */ +bool +sticker_song_find(Directory *directory, const char *name, + void (*func)(Song *song, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx new file mode 100644 index 000000000..4c5f35c78 --- /dev/null +++ b/src/SongUpdate.cxx @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "Directory.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "InputStream.hxx" +#include "DecoderPlugin.hxx" +#include "DecoderList.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagId3.hxx" +#include "tag/ApeTag.hxx" + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> + +Song * +Song::LoadFile(const char *path_utf8, Directory *parent) +{ + Song *song; + bool ret; + + assert((parent == NULL) == g_path_is_absolute(path_utf8)); + assert(!uri_has_scheme(path_utf8)); + assert(strchr(path_utf8, '\n') == NULL); + + song = NewFile(path_utf8, parent); + + //in archive ? + if (parent != NULL && parent->device == DEVICE_INARCHIVE) { + ret = song->UpdateFileInArchive(); + } else { + ret = song->UpdateFile(); + } + if (!ret) { + song->Free(); + return NULL; + } + + return song; +} + +/** + * Attempts to load APE or ID3 tags from the specified file. + */ +static bool +tag_scan_fallback(const char *path, + const struct tag_handler *handler, void *handler_ctx) +{ + return tag_ape_scan2(path, handler, handler_ctx) || + tag_id3_scan(path, handler, handler_ctx); +} + +bool +Song::UpdateFile() +{ + const char *suffix; + const struct decoder_plugin *plugin; + struct stat st; + struct input_stream *is = NULL; + + assert(IsFile()); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + const Path path_fs = map_song_fs(this); + if (path_fs.IsNull()) + return false; + + delete tag; + tag = nullptr; + + if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) { + return false; + } + + mtime = st.st_mtime; + + Mutex mutex; + Cond cond; + + TagBuilder tag_builder; + + do { + /* load file tag */ + if (decoder_plugin_scan_file(plugin, path_fs.c_str(), + &full_tag_handler, &tag_builder)) + break; + + tag_builder.Clear(); + + /* fall back to stream tag */ + if (plugin->scan_stream != NULL) { + /* open the input_stream (if not already + open) */ + if (is == NULL) + is = input_stream::Open(path_fs.c_str(), + mutex, cond, + IgnoreError()); + + /* now try the stream_tag() method */ + if (is != NULL) { + if (decoder_plugin_scan_stream(plugin, is, + &full_tag_handler, + &tag_builder)) + break; + + tag_builder.Clear(); + + is->LockSeek(0, SEEK_SET, IgnoreError()); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) + is->Close(); + + if (!tag_builder.IsDefined()) + return false; + + if (tag_builder.IsEmpty()) + tag_scan_fallback(path_fs.c_str(), &full_tag_handler, + &tag_builder); + + tag = tag_builder.Commit(); + return true; +} + +bool +Song::UpdateFileInArchive() +{ + const char *suffix; + const struct decoder_plugin *plugin; + + assert(IsFile()); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + delete tag; + + //accept every file that has music suffix + //because we don't support tag reading through + //input streams + tag = new Tag(); + + return true; +} diff --git a/src/StateFile.cxx b/src/StateFile.cxx new file mode 100644 index 000000000..ce812076c --- /dev/null +++ b/src/StateFile.cxx @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "StateFile.hxx" +#include "OutputState.hxx" +#include "PlaylistState.hxx" +#include "TextFile.hxx" +#include "Partition.hxx" +#include "Volume.hxx" +#include "event/Loop.hxx" +#include "fs/FileSystem.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <string.h> + +static constexpr Domain state_file_domain("state_file"); + +StateFile::StateFile(Path &&_path, + Partition &_partition, EventLoop &_loop) + :TimeoutMonitor(_loop), + path(std::move(_path)), path_utf8(path.ToUTF8()), + partition(_partition), + prev_volume_version(0), prev_output_version(0), + prev_playlist_version(0) +{ +} + +void +StateFile::RememberVersions() +{ + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&partition.playlist, + &partition.pc); +} + +bool +StateFile::IsModified() const +{ + return prev_volume_version != sw_volume_state_get_hash() || + prev_output_version != audio_output_state_get_version() || + prev_playlist_version != playlist_state_get_hash(&partition.playlist, + &partition.pc); +} + +void +StateFile::Write() +{ + FormatDebug(state_file_domain, + "Saving state file %s", path_utf8.c_str()); + + FILE *fp = FOpen(path, FOpenMode::WriteText); + if (gcc_unlikely(!fp)) { + FormatErrno(state_file_domain, "failed to create %s", + path_utf8.c_str()); + return; + } + + save_sw_volume_state(fp); + audio_output_state_save(fp); + playlist_state_save(fp, &partition.playlist, &partition.pc); + + fclose(fp); + + RememberVersions(); +} + +void +StateFile::Read() +{ + bool success; + + FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str()); + + TextFile file(path); + if (file.HasFailed()) { + FormatErrno(state_file_domain, "failed to open %s", + path_utf8.c_str()); + return; + } + + const char *line; + while ((line = file.ReadLine()) != NULL) { + success = read_sw_volume_state(line) || + audio_output_state_read(line) || + playlist_state_restore(line, file, &partition.playlist, + &partition.pc); + if (!success) + FormatError(state_file_domain, + "Unrecognized line in state file: %s", + line); + } + + RememberVersions(); +} + +void +StateFile::CheckModified() +{ + if (!IsActive() && IsModified()) + ScheduleSeconds(2 * 60); +} + +void +StateFile::OnTimeout() +{ + Write(); +} diff --git a/src/StateFile.hxx b/src/StateFile.hxx new file mode 100644 index 000000000..041650d14 --- /dev/null +++ b/src/StateFile.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_STATE_FILE_HXX +#define MPD_STATE_FILE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "fs/Path.hxx" +#include "gcc.h" + +#include <string> + +struct Partition; + +class StateFile final : private TimeoutMonitor { + Path path; + std::string path_utf8; + + Partition &partition; + + /** + * 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. + */ + unsigned prev_volume_version, prev_output_version, + prev_playlist_version; + +public: + StateFile(Path &&path, Partition &partition, EventLoop &loop); + + void Read(); + void Write(); + + /** + * Schedules a write if MPD's state was modified. + */ + void CheckModified(); + +private: + /** + * Save the current state versions for use with IsModified(). + */ + void RememberVersions(); + + /** + * Check if MPD's state was modified since the last + * RememberVersions() call. + */ + gcc_pure + bool IsModified() const; + + /* virtual methods from TimeoutMonitor */ + virtual void OnTimeout() override; +}; + +#endif /* STATE_FILE_H */ diff --git a/src/Stats.cxx b/src/Stats.cxx new file mode 100644 index 000000000..463937b48 --- /dev/null +++ b/src/Stats.cxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Stats.hxx" +#include "PlayerControl.hxx" +#include "ClientInternal.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSimple.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +struct stats stats; + +void stats_global_init(void) +{ + stats.timer = g_timer_new(); +} + +void stats_global_finish(void) +{ + g_timer_destroy(stats.timer); +} + +void stats_update(void) +{ + Error error; + + DatabaseStats stats2; + + const DatabaseSelection selection("", true); + if (GetDatabase()->GetStats(selection, stats2, error)) { + stats.song_count = stats2.song_count; + stats.song_duration = stats2.total_duration; + stats.artist_count = stats2.artist_count; + stats.album_count = stats2.album_count; + } else { + LogError(error); + + stats.song_count = 0; + stats.song_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + } +} + +void +stats_print(Client *client) +{ + client_printf(client, + "artists: %u\n" + "albums: %u\n" + "songs: %i\n" + "uptime: %li\n" + "playtime: %li\n" + "db_playtime: %li\n", + stats.artist_count, + stats.album_count, + stats.song_count, + (long)g_timer_elapsed(stats.timer, NULL), + (long)(client->player_control->GetTotalPlayTime() + 0.5), + stats.song_duration); + + if (db_is_simple()) + client_printf(client, + "db_update: %li\n", + (long)db_get_mtime()); +} diff --git a/src/Stats.hxx b/src/Stats.hxx new file mode 100644 index 000000000..1827b8847 --- /dev/null +++ b/src/Stats.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_STATS_HXX +#define MPD_STATS_HXX + +class Client; +typedef struct _GTimer GTimer; + +struct stats { + GTimer *timer; + + /** number of song files in the music directory */ + unsigned song_count; + + /** sum of all song durations in the music directory (in + seconds) */ + unsigned long song_duration; + + /** number of distinct artist names in the music directory */ + unsigned artist_count; + + /** number of distinct album names in the music directory */ + unsigned album_count; +}; + +extern struct stats stats; + +void stats_global_init(void); + +void stats_global_finish(void); + +void stats_update(void); + +void +stats_print(Client *client); + +#endif diff --git a/src/StickerCommands.cxx b/src/StickerCommands.cxx new file mode 100644 index 000000000..5fdc0ba15 --- /dev/null +++ b/src/StickerCommands.cxx @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StickerCommands.hxx" +#include "SongPrint.hxx" +#include "DatabaseLock.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "SongSticker.hxx" +#include "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "CommandError.hxx" +#include "protocol/Result.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <string.h> + +struct sticker_song_find_data { + Client *client; + const char *name; +}; + +static void +sticker_song_find_print_cb(Song *song, const char *value, + void *user_data) +{ + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; + + song_print_uri(data->client, song); + sticker_print_value(data->client, data->name, value); +} + +static enum command_return +handle_sticker_song(Client *client, int argc, char *argv[]) +{ + Error error; + const Database *db = GetDatabase(error); + if (db == nullptr) + return print_error(client, error); + + /* get song song_id key */ + if (argc == 5 && strcmp(argv[1], "get") == 0) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + char *value = sticker_song_get_value(song, argv[4]); + db->ReturnSong(song); + if (value == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such sticker"); + return COMMAND_RETURN_ERROR; + } + + sticker_print_value(client, argv[4], value); + g_free(value); + + return COMMAND_RETURN_OK; + /* list song song_id */ + } else if (argc == 4 && strcmp(argv[1], "list") == 0) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + sticker *sticker = sticker_song_get(song); + db->ReturnSong(song); + if (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) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + bool ret = sticker_song_set_value(song, argv[4], argv[5]); + db->ReturnSong(song); + if (!ret) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set sticker value"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + /* delete song song_id [key] */ + } else if ((argc == 4 || argc == 5) && + strcmp(argv[1], "delete") == 0) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + bool ret = argc == 4 + ? sticker_song_delete(song) + : sticker_song_delete_value(song, argv[4]); + db->ReturnSong(song); + if (!ret) { + command_error(client, ACK_ERROR_SYSTEM, + "no such sticker"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + /* find song dir key */ + } else if (argc == 5 && strcmp(argv[1], "find") == 0) { + /* "sticker find song a/directory name" */ + bool success; + struct sticker_song_find_data data = { + client, + argv[4], + }; + + db_lock(); + Directory *directory = db_get_directory(argv[3]); + if (directory == NULL) { + db_unlock(); + command_error(client, ACK_ERROR_NO_EXIST, + "no such directory"); + return COMMAND_RETURN_ERROR; + } + + success = sticker_song_find(directory, data.name, + sticker_song_find_print_cb, &data); + db_unlock(); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set search sticker database"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_ARG, "bad request"); + return COMMAND_RETURN_ERROR; + } +} + +enum command_return +handle_sticker(Client *client, int argc, char *argv[]) +{ + assert(argc >= 4); + + if (!sticker_enabled()) { + command_error(client, ACK_ERROR_UNKNOWN, + "sticker database is disabled"); + return COMMAND_RETURN_ERROR; + } + + if (strcmp(argv[2], "song") == 0) + return handle_sticker_song(client, argc, argv); + else { + command_error(client, ACK_ERROR_ARG, + "unknown sticker domain"); + return COMMAND_RETURN_ERROR; + } +} diff --git a/src/StickerCommands.hxx b/src/StickerCommands.hxx new file mode 100644 index 000000000..840bd33d5 --- /dev/null +++ b/src/StickerCommands.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_COMMANDS_HXX +#define MPD_STICKER_COMMANDS_HXX + +#include "command.h" + +class Client; + +enum command_return +handle_sticker(Client *client, int argc, char *argv[]); + +#endif diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx new file mode 100644 index 000000000..1cd47a605 --- /dev/null +++ b/src/StickerDatabase.cxx @@ -0,0 +1,607 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "StickerDatabase.hxx" +#include "fs/Path.hxx" +#include "Idle.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <string> +#include <map> + +#include <glib.h> +#include <sqlite3.h> +#include <assert.h> + +#if SQLITE_VERSION_NUMBER < 3003009 +#define sqlite3_prepare_v2 sqlite3_prepare +#endif + +struct sticker { + std::map<std::string, std::string> table; +}; + +enum sticker_sql { + STICKER_SQL_GET, + STICKER_SQL_LIST, + STICKER_SQL_UPDATE, + STICKER_SQL_INSERT, + STICKER_SQL_DELETE, + STICKER_SQL_DELETE_VALUE, + STICKER_SQL_FIND, +}; + +static const char *const sticker_sql[] = { + //[STICKER_SQL_GET] = + "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_LIST] = + "SELECT name,value FROM sticker WHERE type=? AND uri=?", + //[STICKER_SQL_UPDATE] = + "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_INSERT] = + "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", + //[STICKER_SQL_DELETE] = + "DELETE FROM sticker WHERE type=? AND uri=?", + //[STICKER_SQL_DELETE_VALUE] = + "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_FIND] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", +}; + +static const char sticker_sql_create[] = + "CREATE TABLE IF NOT EXISTS sticker(" + " type VARCHAR NOT NULL, " + " uri VARCHAR NOT NULL, " + " name VARCHAR NOT NULL, " + " value VARCHAR NOT NULL" + ");" + "CREATE UNIQUE INDEX IF NOT EXISTS" + " sticker_value ON sticker(type, uri, name);" + ""; + +static sqlite3 *sticker_db; +static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)]; + +static constexpr Domain sticker_domain("sticker"); + +static void +LogError(sqlite3 *db, const char *msg) +{ + FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db)); +} + +static sqlite3_stmt * +sticker_prepare(const char *sql, Error &error) +{ + int ret; + sqlite3_stmt *stmt; + + ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + error.Format(sticker_domain, ret, + "sqlite3_prepare_v2() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } + + return stmt; +} + +bool +sticker_global_init(Path &&path, Error &error) +{ + int ret; + + if (path.IsNull()) + /* not configured */ + return true; + + /* open/create the sqlite database */ + + ret = sqlite3_open(path.c_str(), &sticker_db); + if (ret != SQLITE_OK) { + const std::string utf8 = path.ToUTF8(); + error.Format(sticker_domain, ret, + "Failed to open sqlite database '%s': %s", + utf8.c_str(), 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) { + error.Format(sticker_domain, 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], error); + if (sticker_stmt[i] == NULL) + return false; + } + + return true; +} + +void +sticker_global_finish(void) +{ + if (sticker_db == NULL) + /* not configured */ + return; + + for (unsigned i = 0; i < G_N_ELEMENTS(sticker_stmt); ++i) { + assert(sticker_stmt[i] != NULL); + + sqlite3_finalize(sticker_stmt[i]); + } + + sqlite3_close(sticker_db); +} + +bool +sticker_enabled(void) +{ + return sticker_db != NULL; +} + +char * +sticker_load_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; + int ret; + char *value; + + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + + if (*name == 0) + return NULL; + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return NULL; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return NULL; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return NULL; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret == SQLITE_ROW) { + /* record found */ + value = g_strdup((const char*)sqlite3_column_text(stmt, 0)); + } else if (ret == SQLITE_DONE) { + /* no record found */ + value = NULL; + } else { + /* error */ + LogError(sticker_db, "sqlite3_step() failed"); + return NULL; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return value; +} + +static bool +sticker_list_values(std::map<std::string, std::string> &table, + const char *type, const char *uri) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; + int ret; + + assert(type != NULL); + assert(uri != NULL); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + switch (ret) { + const char *name, *value; + + case SQLITE_ROW: + name = (const char*)sqlite3_column_text(stmt, 0); + value = (const char*)sqlite3_column_text(stmt, 1); + + table.insert(std::make_pair(name, value)); + break; + case SQLITE_DONE: + break; + case SQLITE_BUSY: + /* no op */ + break; + default: + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + } while (ret != SQLITE_DONE); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return true; +} + +static bool +sticker_update_value(const char *type, const char *uri, + const char *name, const char *value) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; + int ret; + + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + assert(*name != 0); + assert(value != NULL); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, value, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, name, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + ret = sqlite3_changes(sticker_db); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return ret > 0; +} + +static bool +sticker_insert_value(const char *type, const char *uri, + const char *name, const char *value) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; + int ret; + + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + assert(*name != 0); + assert(value != NULL); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, value, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + + idle_add(IDLE_STICKER); + return true; +} + +bool +sticker_store_value(const char *type, const char *uri, + const char *name, const char *value) +{ + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + assert(name != NULL); + assert(value != NULL); + + if (*name == 0) + return false; + + return sticker_update_value(type, uri, name, value) || + sticker_insert_value(type, uri, name, value); +} + +bool +sticker_delete(const char *type, const char *uri) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; + int ret; + + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return true; +} + +bool +sticker_delete_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; + int ret; + + assert(sticker_enabled()); + assert(type != NULL); + assert(uri != NULL); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + ret = sqlite3_changes(sticker_db); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return ret > 0; +} + +void +sticker_free(struct sticker *sticker) +{ + delete sticker; +} + +const char * +sticker_get_value(const struct sticker *sticker, const char *name) +{ + auto i = sticker->table.find(name); + if (i == sticker->table.end()) + return nullptr; + + return i->second.c_str(); +} + +void +sticker_foreach(const struct sticker *sticker, + void (*func)(const char *name, const char *value, + gpointer user_data), + gpointer user_data) +{ + for (const auto &i : sticker->table) + func(i.first.c_str(), i.second.c_str(), user_data); +} + +struct sticker * +sticker_load(const char *type, const char *uri) +{ + sticker s; + + if (!sticker_list_values(s.table, type, uri)) + return NULL; + + if (s.table.empty()) + /* don't return empty sticker objects */ + return NULL; + + return new sticker(std::move(s)); +} + +bool +sticker_find(const char *type, const char *base_uri, const char *name, + void (*func)(const char *uri, const char *value, + gpointer user_data), + gpointer user_data) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; + int ret; + + assert(type != NULL); + assert(name != NULL); + assert(func != NULL); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + if (base_uri == NULL) + base_uri = ""; + + ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + switch (ret) { + case SQLITE_ROW: + func((const char*)sqlite3_column_text(stmt, 0), + (const char*)sqlite3_column_text(stmt, 1), + user_data); + break; + case SQLITE_DONE: + break; + case SQLITE_BUSY: + /* no op */ + break; + default: + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + } while (ret != SQLITE_DONE); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return true; +} diff --git a/src/StickerDatabase.hxx b/src/StickerDatabase.hxx new file mode 100644 index 000000000..b3f4c63b8 --- /dev/null +++ b/src/StickerDatabase.hxx @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This is the sticker database library. It is the backend of all the + * sticker code in MPD. + * + * "Stickers" are pieces of information attached to existing MPD + * objects (e.g. song files, directories, albums). Clients can create + * arbitrary name/value pairs. MPD itself does not assume any special + * meaning in them. + * + * The goal is to allow clients to share additional (possibly dynamic) + * information about songs, which is neither stored on the client (not + * available to other clients), nor stored in the song files (MPD has + * no write access). + * + * Client developers should create a standard for common sticker + * names, to ensure interoperability. + * + * Examples: song ratings; statistics; deferred tag writes; lyrics; + * ... + * + */ + +#ifndef MPD_STICKER_DATABASE_HXX +#define MPD_STICKER_DATABASE_HXX + +class Error; +class Path; +struct sticker; + +/** + * Opens the sticker database (if path is not NULL). + * + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success, false on error + */ +bool +sticker_global_init(Path &&path, Error &error); + +/** + * Close the sticker database. + */ +void +sticker_global_finish(void); + +/** + * Returns true if the sticker database is configured and available. + */ +bool +sticker_enabled(void); + +/** + * Returns one value from an object's sticker record. The caller must + * free the return value with g_free(). + */ +char * +sticker_load_value(const char *type, const char *uri, const char *name); + +/** + * Sets a sticker value in the specified object. Overwrites existing + * values. + */ +bool +sticker_store_value(const char *type, const char *uri, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All sticker values of the + * specified object are deleted. + */ +bool +sticker_delete(const char *type, const char *uri); + +/** + * Deletes a sticker value. Fails if no sticker with this name + * exists. + */ +bool +sticker_delete_value(const char *type, const char *uri, const char *name); + +/** + * Frees resources held by the sticker object. + * + * @param sticker the sticker object to be freed + */ +void +sticker_free(struct sticker *sticker); + +/** + * Determines a single value in a sticker. + * + * @param sticker the sticker object + * @param name the name of the sticker + * @return the sticker value, or NULL if none was found + */ +const char * +sticker_get_value(const struct sticker *sticker, const char *name); + +/** + * Iterates over all sticker items in a sticker. + * + * @param sticker the sticker object + * @param func a callback function + * @param user_data an opaque pointer for the callback function + */ +void +sticker_foreach(const struct sticker *sticker, + void (*func)(const char *name, const char *value, + void *user_data), + void *user_data); + +/** + * Loads the sticker for the specified resource. + * + * @param type the resource type, e.g. "song" + * @param uri the URI of the resource, e.g. the song path + * @return a sticker object, or NULL on error or if there is no sticker + */ +struct sticker * +sticker_load(const char *type, const char *uri); + +/** + * Finds stickers with the specified name below the specified URI. + * + * @param type the resource type, e.g. "song" + * @param base_uri the URI prefix of the resources, or NULL if all + * resources should be searched + * @param name the name of the sticker + * @return true on success (even if no sticker was found), false on + * failure + */ +bool +sticker_find(const char *type, const char *base_uri, const char *name, + void (*func)(const char *uri, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/StickerPrint.cxx b/src/StickerPrint.cxx new file mode 100644 index 000000000..1708ee4f0 --- /dev/null +++ b/src/StickerPrint.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "Client.hxx" + +void +sticker_print_value(Client *client, + const char *name, const char *value) +{ + client_printf(client, "sticker: %s=%s\n", name, value); +} + +static void +print_sticker_cb(const char *name, const char *value, void *data) +{ + Client *client = (Client *)data; + + sticker_print_value(client, name, value); +} + +void +sticker_print(Client *client, const struct sticker *sticker) +{ + sticker_foreach(sticker, print_sticker_cb, client); +} diff --git a/src/StickerPrint.hxx b/src/StickerPrint.hxx new file mode 100644 index 000000000..27225a494 --- /dev/null +++ b/src/StickerPrint.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_STICKER_PRINT_HXX +#define MPD_STICKER_PRINT_HXX + +struct sticker; +class Client; + +/** + * Sends one sticker value to the client. + */ +void +sticker_print_value(Client *client, const char *name, const char *value); + +/** + * Sends all sticker values to the client. + */ +void +sticker_print(Client *client, const struct sticker *sticker); + +#endif diff --git a/src/TagFile.cxx b/src/TagFile.cxx new file mode 100644 index 000000000..d5ba40d9e --- /dev/null +++ b/src/TagFile.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagFile.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "InputStream.hxx" + +#include <assert.h> +#include <unistd.h> /* for SEEK_SET */ + +bool +tag_file_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + assert(path_fs != NULL); + assert(handler != NULL); + + /* check if there's a suffix and a plugin */ + + const char *suffix = uri_get_suffix(path_fs); + if (suffix == NULL) + return false; + + const struct decoder_plugin *plugin = + decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + struct input_stream *is = NULL; + Mutex mutex; + Cond cond; + + do { + /* load file tag */ + if (decoder_plugin_scan_file(plugin, path_fs, + handler, handler_ctx)) + break; + + /* fall back to stream tag */ + if (plugin->scan_stream != NULL) { + /* open the input_stream (if not already + open) */ + if (is == nullptr) { + Error error; + is = input_stream::Open(path_fs, mutex, cond, + error); + } + + /* now try the stream_tag() method */ + if (is != NULL) { + if (decoder_plugin_scan_stream(plugin, is, + handler, + handler_ctx)) + break; + + is->LockSeek(0, SEEK_SET, IgnoreError()); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) + is->Close(); + + return plugin != NULL; +} diff --git a/src/TagFile.hxx b/src/TagFile.hxx new file mode 100644 index 000000000..910f7b82b --- /dev/null +++ b/src/TagFile.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_FILE_HXX +#define MPD_TAG_FILE_HXX + +#include "check.h" + +struct tag_handler; + +/** + * Scan the tags of a song file. Invokes matching decoder plugins, + * but does not invoke the special "APE" and "ID3" scanners. + */ +bool +tag_file_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx new file mode 100644 index 000000000..e3c2c8c9e --- /dev/null +++ b/src/TagPrint.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagPrint.hxx" +#include "tag/Tag.hxx" +#include "tag/TagSettings.h" +#include "Song.hxx" +#include "Client.hxx" + +void tag_print_types(Client *client) +{ + int i; + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (!ignore_tag_items[i]) + client_printf(client, "tagtype: %s\n", + tag_item_names[i]); + } +} + +void tag_print(Client *client, const Tag &tag) +{ + if (tag.time >= 0) + client_printf(client, SONG_TIME "%i\n", tag.time); + + for (unsigned i = 0; i < tag.num_items; i++) { + client_printf(client, "%s: %s\n", + tag_item_names[tag.items[i]->type], + tag.items[i]->value); + } +} diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx new file mode 100644 index 000000000..48bfc72cc --- /dev/null +++ b/src/TagPrint.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PRINT_HXX +#define MPD_TAG_PRINT_HXX + +struct Tag; +class Client; + +void tag_print_types(Client *client); + +void +tag_print(Client *client, const Tag &tag); + +#endif diff --git a/src/TagSave.cxx b/src/TagSave.cxx new file mode 100644 index 000000000..b20d986c2 --- /dev/null +++ b/src/TagSave.cxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagSave.hxx" +#include "tag/Tag.hxx" +#include "Song.hxx" + +void +tag_save(FILE *file, const Tag &tag) +{ + if (tag.time >= 0) + fprintf(file, SONG_TIME "%i\n", tag.time); + + if (tag.has_playlist) + fprintf(file, "Playlist: yes\n"); + + for (unsigned i = 0; i < tag.num_items; i++) + fprintf(file, "%s: %s\n", + tag_item_names[tag.items[i]->type], + tag.items[i]->value); +} diff --git a/src/TagSave.hxx b/src/TagSave.hxx new file mode 100644 index 000000000..0b1359c89 --- /dev/null +++ b/src/TagSave.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SAVE_HXX +#define MPD_TAG_SAVE_HXX + +#include <stdio.h> + +struct Tag; + +void +tag_save(FILE *file, const Tag &tag); + +#endif diff --git a/src/TextFile.cxx b/src/TextFile.cxx new file mode 100644 index 000000000..da0b33816 --- /dev/null +++ b/src/TextFile.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TextFile.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +TextFile::TextFile(const Path &path_fs) + :file(FOpen(path_fs, FOpenMode::ReadText)), + buffer(g_string_sized_new(step)) {} + +TextFile::~TextFile() +{ + if (file != nullptr) + fclose(file); + + g_string_free(buffer, true); +} + +char * +TextFile::ReadLine() +{ + gsize length = 0, i; + char *p; + + assert(file != NULL); + assert(buffer != NULL); + assert(buffer->allocated_len >= 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/TextFile.hxx b/src/TextFile.hxx new file mode 100644 index 000000000..6aff4ca70 --- /dev/null +++ b/src/TextFile.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_TEXT_FILE_HXX + +#include "gcc.h" + +#include <stdio.h> + +class Path; +typedef struct _GString GString; + +class TextFile { + static constexpr size_t max_length = 512 * 1024; + static constexpr size_t step = 1024; + + FILE *const file; + + GString *const buffer; + +public: + TextFile(const Path &path_fs); + + TextFile(const TextFile &other) = delete; + + ~TextFile(); + + bool HasFailed() const { + return gcc_unlikely(file == nullptr); + } + + /** + * 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 *ReadLine(); +}; + +#endif diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx new file mode 100644 index 000000000..d58a2fbee --- /dev/null +++ b/src/TextInputStream.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TextInputStream.hxx" +#include "InputStream.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +TextInputStream::TextInputStream(struct input_stream *_is) + : is(_is), + buffer(fifo_buffer_new(4096)) +{ +} + +TextInputStream::~TextInputStream() +{ + fifo_buffer_free(buffer); +} + +bool TextInputStream::ReadLine(std::string &line) +{ + void *dest; + const char *src, *p; + size_t length, nbytes; + + do { + dest = fifo_buffer_write(buffer, &length); + if (dest != nullptr && length >= 2) { + /* reserve one byte for the null terminator if + the last line is not terminated by a + newline character */ + --length; + + Error error; + nbytes = is->LockRead(dest, length, error); + if (nbytes > 0) + fifo_buffer_append(buffer, nbytes); + else if (error.IsDefined()) { + LogError(error); + return false; + } + } else + nbytes = 0; + + auto src_p = fifo_buffer_read(buffer, &length); + src = reinterpret_cast<const char *>(src_p); + + if (src == nullptr) + return false; + + p = reinterpret_cast<const char*>(memchr(src, '\n', length)); + if (p == nullptr && nbytes == 0) { + /* end of file (or line too long): terminate + the current line */ + dest = fifo_buffer_write(buffer, &nbytes); + assert(dest != nullptr); + *(char *)dest = '\n'; + fifo_buffer_append(buffer, 1); + } + } while (p == nullptr); + + length = p - src + 1; + while (p > src && g_ascii_isspace(p[-1])) + --p; + + line = std::string(src, p - src); + fifo_buffer_consume(buffer, length); + return true; +} diff --git a/src/TextInputStream.hxx b/src/TextInputStream.hxx new file mode 100644 index 000000000..2608184e2 --- /dev/null +++ b/src/TextInputStream.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_INPUT_STREAM_HXX +#define MPD_TEXT_INPUT_STREAM_HXX + +#include <string> + +struct input_stream; +struct fifo_buffer; + +class TextInputStream { + struct input_stream *is; + struct fifo_buffer *buffer; +public: + /** + * Wraps an existing #input_stream object into a #TextInputStream, + * to read its contents as text lines. + * + * @param _is an open #input_stream object + */ + explicit TextInputStream(struct input_stream *_is); + + /** + * Frees the #TextInputStream object. Does not close or free the + * underlying #input_stream. + */ + ~TextInputStream(); + + TextInputStream(const TextInputStream &) = delete; + TextInputStream& operator=(const TextInputStream &) = delete; + + /** + * Reads the next line from the stream with newline character stripped. + * + * @param line a string to put result to + * @return true if line is read successfully, false on end of file + * or error + */ + bool ReadLine(std::string &line); +}; + +#endif diff --git a/src/TimePrint.cxx b/src/TimePrint.cxx new file mode 100644 index 000000000..c5247dd9d --- /dev/null +++ b/src/TimePrint.cxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TimePrint.hxx" +#include "Client.hxx" + +#include <glib.h> + +void +time_print(Client *client, const char *name, time_t t) +{ +#ifdef G_OS_WIN32 + const struct tm *tm2 = gmtime(&t); +#else + struct tm tm; + const struct tm *tm2 = gmtime_r(&t, &tm); +#endif + if (tm2 == NULL) + return; + + char buffer[32]; + strftime(buffer, sizeof(buffer), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", +#else + "%FT%TZ", +#endif + tm2); + client_printf(client, "%s: %s\n", name, buffer); +} diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx new file mode 100644 index 000000000..f45101256 --- /dev/null +++ b/src/TimePrint.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_TIME_PRINT_HXX +#define MPD_TIME_PRINT_HXX + +#include <time.h> + +class Client; + +/** + * Write a line with a time stamp to the client. + */ +void +time_print(Client *client, const char *name, time_t t); + +#endif diff --git a/src/Timer.cxx b/src/Timer.cxx new file mode 100644 index 000000000..75fba03aa --- /dev/null +++ b/src/Timer.cxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Timer.hxx" +#include "AudioFormat.hxx" +#include "system/clock.h" + +#include <glib.h> + +#include <assert.h> +#include <limits.h> +#include <stddef.h> + +Timer::Timer(const AudioFormat af) + : time(0), + started(false), + rate(af.sample_rate * af.GetFrameSize()) +{ +} + +void Timer::Start() +{ + time = monotonic_clock_us(); + started = true; +} + +void Timer::Reset() +{ + time = 0; + started = false; +} + +void Timer::Add(int size) +{ + assert(started); + + // (size samples) / (rate samples per second) = duration seconds + // duration seconds * 1000000 = duration us + time += ((uint64_t)size * 1000000) / rate; +} + +unsigned Timer::GetDelay() const +{ + int64_t delay = (int64_t)(time - monotonic_clock_us()) / 1000; + if (delay < 0) + return 0; + + if (delay > G_MAXINT) + delay = G_MAXINT; + + return delay; +} + +void Timer::Synchronize() const +{ + int64_t sleep_duration; + + assert(started); + + sleep_duration = time - monotonic_clock_us(); + if (sleep_duration > 0) + g_usleep(sleep_duration); +} diff --git a/src/Timer.hxx b/src/Timer.hxx new file mode 100644 index 000000000..ccc29b873 --- /dev/null +++ b/src/Timer.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TIMER_HXX +#define MPD_TIMER_HXX + +#include <stdint.h> + +struct AudioFormat; + +class Timer { + uint64_t time; + bool started; + const int rate; +public: + explicit Timer(AudioFormat af); + + bool IsStarted() const { return started; } + + void Start(); + void Reset(); + + void Add(int size); + + /** + * Returns the number of milliseconds to sleep to get back to sync. + */ + unsigned GetDelay() const; + + void Synchronize() const; +}; + +#endif diff --git a/src/UpdateArchive.cxx b/src/UpdateArchive.cxx new file mode 100644 index 000000000..b920b8303 --- /dev/null +++ b/src/UpdateArchive.cxx @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UpdateArchive.hxx" +#include "UpdateInternal.hxx" +#include "UpdateDomain.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string.h> + +static void +update_archive_tree(Directory *directory, const char *name) +{ + const char *tmp = strchr(name, '/'); + if (tmp) { + char *child_name = g_strndup(name, tmp - name); + //add dir is not there already + db_lock(); + Directory *subdir = + directory->MakeChild(child_name); + subdir->device = DEVICE_INARCHIVE; + db_unlock(); + g_free(child_name); + + //create directories first + update_archive_tree(subdir, tmp+1); + } else { + if (strlen(name) == 0) { + LogWarning(update_domain, + "archive returned directory only"); + return; + } + + //add file + db_lock(); + Song *song = directory->FindSong(name); + db_unlock(); + if (song == NULL) { + song = Song::LoadFile(name, directory); + if (song != NULL) { + db_lock(); + directory->AddSong(song); + db_unlock(); + + modified = true; + FormatInfo(update_domain, "added %s/%s", + directory->GetPath(), 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_file2(Directory *parent, const char *name, + const struct stat *st, + const struct archive_plugin *plugin) +{ + db_lock(); + Directory *directory = parent->FindChild(name); + db_unlock(); + + 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; + + const Path path_fs = map_directory_child_fs(parent, name); + + /* open archive */ + Error error; + ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), error); + if (file == NULL) { + LogError(error); + return; + } + + FormatDebug(update_domain, "archive %s opened", path_fs.c_str()); + + if (directory == NULL) { + FormatDebug(update_domain, + "creating archive directory: %s", name); + db_lock(); + directory = parent->CreateChild(name); + /* mark this directory as archive (we use device for + this) */ + directory->device = DEVICE_INARCHIVE; + db_unlock(); + } + + directory->mtime = st->st_mtime; + + class UpdateArchiveVisitor final : public ArchiveVisitor { + Directory *directory; + + public: + UpdateArchiveVisitor(Directory *_directory) + :directory(_directory) {} + + virtual void VisitArchiveEntry(const char *path_utf8) override { + FormatDebug(update_domain, + "adding archive file: %s", path_utf8); + update_archive_tree(directory, path_utf8); + } + }; + + UpdateArchiveVisitor visitor(directory); + file->Visit(visitor); + file->Close(); +} + +bool +update_archive_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st) +{ +#ifdef ENABLE_ARCHIVE + const struct archive_plugin *plugin = + archive_plugin_from_suffix(suffix); + if (plugin == NULL) + return false; + + update_archive_file2(directory, name, st, plugin); + return true; +#else + (void)directory; + (void)name; + (void)suffix; + (void)st; + + return false; +#endif +} diff --git a/src/UpdateArchive.hxx b/src/UpdateArchive.hxx new file mode 100644 index 000000000..73b363d27 --- /dev/null +++ b/src/UpdateArchive.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ARCHIVE_HXX +#define MPD_UPDATE_ARCHIVE_HXX + +#include "check.h" +#include "gcc.h" + +#include <sys/stat.h> + +struct Directory; +struct archive_plugin; + +#ifdef ENABLE_ARCHIVE + +bool +update_archive_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st); + +#else + +static inline bool +update_archive_file(gcc_unused Directory *directory, + gcc_unused const char *name, + gcc_unused const char *suffix, + gcc_unused const struct stat *st) +{ + return false; +} + +#endif + +#endif diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx new file mode 100644 index 000000000..082e34ffe --- /dev/null +++ b/src/UpdateContainer.cxx @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UpdateContainer.hxx" +#include "UpdateInternal.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateDomain.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "DecoderPlugin.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "Log.hxx" + +#include <glib.h> + +/** + * Create the specified directory object if it does not exist already + * or if the #stat object indicates that it has been modified since + * the last update. Returns NULL when it exists already and is + * unmodified. + * + * The caller must lock the database. + */ +static Directory * +make_directory_if_modified(Directory *parent, const char *name, + const struct stat *st) +{ + Directory *directory = parent->FindChild(name); + + // directory exists already + if (directory != NULL) { + if (directory->mtime == st->st_mtime && !walk_discard) { + /* not modified */ + return NULL; + } + + delete_directory(directory); + modified = true; + } + + directory = parent->MakeChild(name); + directory->mtime = st->st_mtime; + return directory; +} + +bool +update_container_file(Directory *directory, + const char *name, + const struct stat *st, + const struct decoder_plugin *plugin) +{ + if (plugin->container_scan == NULL) + return false; + + db_lock(); + Directory *contdir = make_directory_if_modified(directory, name, st); + if (contdir == NULL) { + /* not modified */ + db_unlock(); + return true; + } + + contdir->device = DEVICE_CONTAINER; + db_unlock(); + + const Path pathname = map_directory_child_fs(directory, name); + + char *vtrack; + unsigned int tnum = 0; + TagBuilder tag_builder; + while ((vtrack = plugin->container_scan(pathname.c_str(), ++tnum)) != NULL) { + Song *song = Song::NewFile(vtrack, contdir); + + // shouldn't be necessary but it's there.. + song->mtime = st->st_mtime; + + const Path child_path_fs = + map_directory_child_fs(contdir, vtrack); + + decoder_plugin_scan_file(plugin, child_path_fs.c_str(), + &add_tag_handler, &tag_builder); + + if (tag_builder.IsDefined()) + song->tag = tag_builder.Commit(); + else + tag_builder.Clear(); + + db_lock(); + contdir->AddSong(song); + db_unlock(); + + modified = true; + + FormatInfo(update_domain, "added %s/%s", + directory->GetPath(), vtrack); + g_free(vtrack); + } + + if (tnum == 1) { + db_lock(); + delete_directory(contdir); + db_unlock(); + return false; + } else + return true; +} diff --git a/src/UpdateContainer.hxx b/src/UpdateContainer.hxx new file mode 100644 index 000000000..0078730d6 --- /dev/null +++ b/src/UpdateContainer.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONTAINER_HXX +#define MPD_UPDATE_CONTAINER_HXX + +#include "check.h" + +#include <sys/stat.h> + +struct Directory; +struct decoder_plugin; + +bool +update_container_file(Directory *directory, + const char *name, + const struct stat *st, + const struct decoder_plugin *plugin); + +#endif diff --git a/src/UpdateDatabase.cxx b/src/UpdateDatabase.cxx new file mode 100644 index 000000000..9d2fa9017 --- /dev/null +++ b/src/UpdateDatabase.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateDatabase.hxx" +#include "UpdateRemove.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "DatabaseLock.hxx" + +#include <glib.h> +#include <assert.h> + +void +delete_song(Directory *dir, Song *del) +{ + assert(del->parent == dir); + + /* first, prevent traversers in main task from getting this */ + dir->RemoveSong(del); + + db_unlock(); /* temporary unlock, because update_remove_song() blocks */ + + /* now take it out of the playlist (in the main_task) */ + update_remove_song(del); + + /* finally, all possible references gone, free it */ + del->Free(); + + db_lock(); +} + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + * + * Caller must lock the #db_mutex. + */ +static void +clear_directory(Directory *directory) +{ + Directory *child, *n; + directory_for_each_child_safe(child, n, directory) + delete_directory(child); + + Song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + assert(song->parent == directory); + delete_song(directory, song); + } +} + +void +delete_directory(Directory *directory) +{ + assert(directory->parent != NULL); + + clear_directory(directory); + + directory->Delete(); +} + +bool +delete_name_in(Directory *parent, const char *name) +{ + bool modified = false; + + db_lock(); + Directory *directory = parent->FindChild(name); + + if (directory != NULL) { + delete_directory(directory); + modified = true; + } + + Song *song = parent->FindSong(name); + if (song != NULL) { + delete_song(parent, song); + modified = true; + } + + parent->playlists.erase(name); + + db_unlock(); + + return modified; +} diff --git a/src/UpdateDatabase.hxx b/src/UpdateDatabase.hxx new file mode 100644 index 000000000..ab8f7ec26 --- /dev/null +++ b/src/UpdateDatabase.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DATABASE_HXX +#define MPD_UPDATE_DATABASE_HXX + +#include "check.h" + +struct Directory; +struct Song; + +/** + * Caller must lock the #db_mutex. + */ +void +delete_song(Directory *parent, Song *song); + +/** + * Recursively free a directory and all its contents. + * + * Caller must lock the #db_mutex. + */ +void +delete_directory(Directory *directory); + +/** + * Caller must NOT lock the #db_mutex. + * + * @return true if the database was modified + */ +bool +delete_name_in(Directory *parent, const char *name); + +#endif diff --git a/src/UpdateDomain.cxx b/src/UpdateDomain.cxx new file mode 100644 index 000000000..a2bbd5b70 --- /dev/null +++ b/src/UpdateDomain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UpdateDomain.hxx" +#include "util/Domain.hxx" + +const Domain update_domain("update"); diff --git a/src/UpdateDomain.hxx b/src/UpdateDomain.hxx new file mode 100644 index 000000000..e7528a57e --- /dev/null +++ b/src/UpdateDomain.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DOMAIN_HXX +#define MPD_UPDATE_DOMAIN_HXX + +extern const class Domain update_domain; + +#endif diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx new file mode 100644 index 000000000..50af6271d --- /dev/null +++ b/src/UpdateGlue.cxx @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UpdateGlue.hxx" +#include "UpdateQueue.hxx" +#include "UpdateWalk.hxx" +#include "UpdateRemove.hxx" +#include "UpdateDomain.hxx" +#include "Mapper.hxx" +#include "DatabaseSimple.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Stats.hxx" +#include "Main.hxx" +#include "Instance.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <assert.h> + +static enum update_progress { + UPDATE_PROGRESS_IDLE = 0, + UPDATE_PROGRESS_RUNNING = 1, + UPDATE_PROGRESS_DONE = 2 +} progress; + +static bool modified; + +static GThread *update_thr; + +static const unsigned update_task_id_max = 1 << 15; + +static unsigned update_task_id; + +/* XXX this flag is passed to update_task() */ +static bool discard; + +unsigned +isUpdatingDB(void) +{ + return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; +} + +static void * update_task(void *_path) +{ + const char *path = (const char *)_path; + + if (path != NULL && *path != 0) + FormatDebug(update_domain, "starting: %s", path); + else + LogDebug(update_domain, "starting"); + + modified = update_walk(path, discard); + + if (modified || !db_exists()) { + Error error; + if (!db_save(error)) + LogError(error, "Failed to save database"); + } + + if (path != NULL && *path != 0) + FormatDebug(update_domain, "finished: %s", path); + else + LogDebug(update_domain, "finished"); + g_free(_path); + + progress = UPDATE_PROGRESS_DONE; + GlobalEvents::Emit(GlobalEvents::UPDATE); + return NULL; +} + +static void +spawn_update_task(const char *path) +{ + assert(g_thread_self() == main_task); + + progress = UPDATE_PROGRESS_RUNNING; + modified = false; + +#if GLIB_CHECK_VERSION(2,32,0) + update_thr = g_thread_new("update", update_task, g_strdup(path)); +#else + GError *e = NULL; + update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e); + if (update_thr == NULL) + FatalError("Failed to spawn update task", e); +#endif + + if (++update_task_id > update_task_id_max) + update_task_id = 1; + FormatDebug(update_domain, + "spawned thread for update job id %i", update_task_id); +} + +unsigned +update_enqueue(const char *path, bool _discard) +{ + assert(g_thread_self() == main_task); + + if (!db_is_simple() || !mapper_has_music_directory()) + return 0; + + if (progress != UPDATE_PROGRESS_IDLE) { + unsigned next_task_id = + update_queue_push(path, discard, update_task_id); + if (next_task_id == 0) + return 0; + + return next_task_id > update_task_id_max ? 1 : next_task_id; + } + + discard = _discard; + spawn_update_task(path); + + idle_add(IDLE_UPDATE); + + return update_task_id; +} + +/** + * Called in the main thread after the database update is finished. + */ +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 */ + instance->DatabaseModified(); + + path = update_queue_shift(&discard); + if (path != NULL) { + /* schedule the next path */ + spawn_update_task(path); + g_free(path); + } else { + progress = UPDATE_PROGRESS_IDLE; + + stats_update(); + } +} + +void update_global_init(void) +{ + GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event); + + update_remove_global_init(); + update_walk_global_init(); +} + +void update_global_finish(void) +{ + update_walk_global_finish(); +} diff --git a/src/UpdateGlue.hxx b/src/UpdateGlue.hxx new file mode 100644 index 000000000..9d546a2a3 --- /dev/null +++ b/src/UpdateGlue.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_GLUE_HXX +#define MPD_UPDATE_GLUE_HXX + +void update_global_init(void); + +void update_global_finish(void); + +unsigned +isUpdatingDB(void); + +/** + * 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 +update_enqueue(const char *path, bool discard); + +#endif diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx new file mode 100644 index 000000000..ba4fcb7cf --- /dev/null +++ b/src/UpdateIO.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateIO.hxx" +#include "src/UpdateDomain.hxx" +#include "Directory.hxx" +#include "Mapper.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "Log.hxx" + +#include <errno.h> +#include <unistd.h> + +int +stat_directory(const Directory *directory, struct stat *st) +{ + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + return -1; + + if (!StatFile(path_fs, *st)) { + int error = errno; + const std::string path_utf8 = path_fs.ToUTF8(); + FormatErrno(update_domain, error, + "Failed to stat %s", path_utf8.c_str()); + return -1; + } + + return 0; +} + +int +stat_directory_child(const Directory *parent, const char *name, + struct stat *st) +{ + const Path path_fs = map_directory_child_fs(parent, name); + if (path_fs.IsNull()) + return -1; + + if (!StatFile(path_fs, *st)) { + int error = errno; + const std::string path_utf8 = path_fs.ToUTF8(); + FormatErrno(update_domain, error, + "Failed to stat %s", path_utf8.c_str()); + return -1; + } + + return 0; +} + +bool +directory_exists(const Directory *directory) +{ + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + /* invalid path: cannot exist */ + return false; + + return directory->device == DEVICE_INARCHIVE || + directory->device == DEVICE_CONTAINER + ? FileExists(path_fs) + : DirectoryExists(path_fs); +} + +bool +directory_child_is_regular(const Directory *directory, + const char *name_utf8) +{ + const Path path_fs = map_directory_child_fs(directory, name_utf8); + if (path_fs.IsNull()) + return false; + + return FileExists(path_fs); +} + +bool +directory_child_access(const Directory *directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* CheckAccess() is useless on WIN32 */ + (void)directory; + (void)name; + (void)mode; + return true; +#else + const Path path = map_directory_child_fs(directory, name); + if (path.IsNull()) + /* something went wrong, but that isn't a permission + problem */ + return true; + + return CheckAccess(path, mode) || errno != EACCES; +#endif +} diff --git a/src/UpdateIO.hxx b/src/UpdateIO.hxx new file mode 100644 index 000000000..ee47b2682 --- /dev/null +++ b/src/UpdateIO.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_IO_HXX +#define MPD_UPDATE_IO_HXX + +#include "check.h" + +#include <sys/stat.h> + +struct Directory; + +int +stat_directory(const Directory *directory, struct stat *st); + +int +stat_directory_child(const Directory *parent, const char *name, + struct stat *st); + +bool +directory_exists(const Directory *directory); + +bool +directory_child_is_regular(const Directory *directory, + const char *name_utf8); + +/** + * Checks if the given permissions on the mapped file are given. + */ +bool +directory_child_access(const Directory *directory, + const char *name, int mode); + +#endif diff --git a/src/UpdateInternal.hxx b/src/UpdateInternal.hxx new file mode 100644 index 000000000..50443c086 --- /dev/null +++ b/src/UpdateInternal.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_INTERNAL_H +#define MPD_UPDATE_INTERNAL_H + +#include "check.h" + +extern bool walk_discard; +extern bool modified; + +#endif diff --git a/src/UpdateQueue.cxx b/src/UpdateQueue.cxx new file mode 100644 index 000000000..85eb22358 --- /dev/null +++ b/src/UpdateQueue.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UpdateQueue.hxx" + +#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/UpdateQueue.hxx b/src/UpdateQueue.hxx new file mode 100644 index 000000000..7de06964f --- /dev/null +++ b/src/UpdateQueue.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_QUEUE_HXX +#define MPD_UPDATE_QUEUE_HXX + +#include "check.h" + +unsigned +update_queue_push(const char *path, bool discard, unsigned base); + +char * +update_queue_shift(bool *discard_r); + +#endif diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx new file mode 100644 index 000000000..d43ad92c7 --- /dev/null +++ b/src/UpdateRemove.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateRemove.hxx" +#include "UpdateDomain.hxx" +#include "Playlist.hxx" +#include "GlobalEvents.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "Song.hxx" +#include "Main.hxx" +#include "Instance.hxx" +#include "Log.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#include "SongSticker.hxx" +#endif + +#include <glib.h> + +#include <assert.h> + +static const Song *removed_song; + +static Mutex remove_mutex; +static Cond remove_cond; + +/** + * 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 = removed_song->GetURI(); + FormatInfo(update_domain, "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 + + instance->DeleteSong(*removed_song); + + /* clear "removed_song" and send signal to update thread */ + remove_mutex.lock(); + removed_song = NULL; + remove_cond.signal(); + remove_mutex.unlock(); +} + +void +update_remove_global_init(void) +{ + GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event); +} + +void +update_remove_song(const Song *song) +{ + assert(removed_song == NULL); + + removed_song = song; + + GlobalEvents::Emit(GlobalEvents::DELETE); + + remove_mutex.lock(); + + while (removed_song != NULL) + remove_cond.wait(remove_mutex); + + remove_mutex.unlock(); +} diff --git a/src/UpdateRemove.hxx b/src/UpdateRemove.hxx new file mode 100644 index 000000000..bef27d766 --- /dev/null +++ b/src/UpdateRemove.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_REMOVE_HXX +#define MPD_UPDATE_REMOVE_HXX + +#include "check.h" + +struct Song; + +void +update_remove_global_init(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 Song *song); + +#endif diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx new file mode 100644 index 000000000..3aef2b560 --- /dev/null +++ b/src/UpdateSong.cxx @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateSong.hxx" +#include "UpdateInternal.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateContainer.hxx" +#include "UpdateDomain.hxx" +#include "DatabaseLock.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "DecoderPlugin.hxx" +#include "DecoderList.hxx" +#include "Log.hxx" + +#include <unistd.h> + +static void +update_song_file2(Directory *directory, + const char *name, const struct stat *st, + const struct decoder_plugin *plugin) +{ + db_lock(); + Song *song = directory->FindSong(name); + db_unlock(); + + if (!directory_child_access(directory, name, R_OK)) { + FormatError(update_domain, + "no read permissions on %s/%s", + directory->GetPath(), name); + if (song != NULL) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (!(song != NULL && st->st_mtime == song->mtime && + !walk_discard) && + update_container_file(directory, name, st, plugin)) { + if (song != NULL) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (song == NULL) { + FormatDebug(update_domain, "reading %s/%s", + directory->GetPath(), name); + song = Song::LoadFile(name, directory); + if (song == NULL) { + FormatDebug(update_domain, + "ignoring unrecognized file %s/%s", + directory->GetPath(), name); + return; + } + + db_lock(); + directory->AddSong(song); + db_unlock(); + + modified = true; + FormatInfo(update_domain, "added %s/%s", + directory->GetPath(), name); + } else if (st->st_mtime != song->mtime || walk_discard) { + FormatInfo(update_domain, "updating %s/%s", + directory->GetPath(), name); + if (!song->UpdateFile()) { + FormatDebug(update_domain, + "deleting unrecognized file %s/%s", + directory->GetPath(), name); + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + modified = true; + } +} + +bool +update_song_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st) +{ + const struct decoder_plugin *plugin = + decoder_plugin_from_suffix(suffix, nullptr); + if (plugin == NULL) + return false; + + update_song_file2(directory, name, st, plugin); + return true; +} diff --git a/src/UpdateSong.hxx b/src/UpdateSong.hxx new file mode 100644 index 000000000..60a532e3a --- /dev/null +++ b/src/UpdateSong.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SONG_HXX +#define MPD_UPDATE_SONG_HXX + +#include "check.h" + +#include <sys/stat.h> + +struct Directory; + +bool +update_song_file(Directory *directory, + const char *name, const char *suffix, + const struct stat *st); + +#endif diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx new file mode 100644 index 000000000..cfa68dcf4 --- /dev/null +++ b/src/UpdateWalk.cxx @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UpdateWalk.hxx" +#include "UpdateIO.hxx" +#include "UpdateDatabase.hxx" +#include "UpdateSong.hxx" +#include "UpdateArchive.hxx" +#include "UpdateDomain.hxx" +#include "DatabaseLock.hxx" +#include "DatabaseSimple.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "PlaylistVector.hxx" +#include "PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "ExcludeList.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "fs/DirectoryReader.hxx" +#include "util/UriUtil.hxx" +#include "Log.hxx" + +#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> + +bool walk_discard; +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(Directory *dir, const struct stat *st) +{ + dir->inode = st->st_ino; + dir->device = st->st_dev; + dir->have_stat = true; +} + +static void +remove_excluded_from_directory(Directory *directory, + const ExcludeList &exclude_list) +{ + db_lock(); + + Directory *child, *n; + directory_for_each_child_safe(child, n, directory) { + const Path name_fs = Path::FromUTF8(child->GetName()); + + if (name_fs.IsNull() || exclude_list.Check(name_fs)) { + delete_directory(child); + modified = true; + } + } + + Song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + assert(song->parent == directory); + + const Path name_fs = Path::FromUTF8(song->uri); + if (name_fs.IsNull() || exclude_list.Check(name_fs)) { + delete_song(directory, song); + modified = true; + } + } + + db_unlock(); +} + +static void +purge_deleted_from_directory(Directory *directory) +{ + Directory *child, *n; + directory_for_each_child_safe(child, n, directory) { + if (directory_exists(child)) + continue; + + db_lock(); + delete_directory(child); + db_unlock(); + + modified = true; + } + + Song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + const Path path = map_song_fs(song); + if (path.IsNull() || !FileExists(path)) { + db_lock(); + delete_song(directory, song); + db_unlock(); + + modified = true; + } + } + + for (auto i = directory->playlists.begin(), + end = directory->playlists.end(); + i != end;) { + if (!directory_child_is_regular(directory, i->name.c_str())) { + db_lock(); + i = directory->playlists.erase(i); + db_unlock(); + } else + ++i; + } +} + +#ifndef G_OS_WIN32 +static int +update_directory_stat(Directory *directory) +{ + struct stat st; + if (stat_directory(directory, &st) < 0) + return -1; + + directory_set_stat(directory, &st); + return 0; +} +#endif + +static int +find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) +{ +#ifndef G_OS_WIN32 + while (parent) { + if (!parent->have_stat && update_directory_stat(parent) < 0) + return -1; + + if (parent->inode == inode && parent->device == device) { + LogDebug(update_domain, "recursive directory found"); + return 1; + } + + parent = parent->parent; + } +#else + (void)parent; + (void)inode; + (void)device; +#endif + + return 0; +} + +static bool +update_playlist_file2(Directory *directory, + const char *name, const char *suffix, + const struct stat *st) +{ + if (!playlist_suffix_supported(suffix)) + return false; + + PlaylistInfo pi(name, st->st_mtime); + + db_lock(); + if (directory->playlists.UpdateOrInsert(std::move(pi))) + modified = true; + db_unlock(); + return true; +} + +static bool +update_regular_file(Directory *directory, + const char *name, const struct stat *st) +{ + const char *suffix = uri_get_suffix(name); + if (suffix == NULL) + return false; + + return update_song_file(directory, name, suffix, st) || + update_archive_file(directory, name, suffix, st) || + update_playlist_file2(directory, name, suffix, st); +} + +static bool +update_directory(Directory *directory, const struct stat *st); + +static void +update_directory_child(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)) { + if (find_inode_ancestor(directory, st->st_ino, st->st_dev)) + return; + + db_lock(); + Directory *subdir = directory->MakeChild(name); + db_unlock(); + + assert(directory == subdir->parent); + + if (!update_directory(subdir, st)) { + db_lock(); + delete_directory(subdir); + db_unlock(); + } + } else { + FormatDebug(update_domain, + "%s is not a directory, archive or music", name); + } +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +gcc_pure +static bool skip_path(const Path &path_fs) +{ + const char *path = path_fs.c_str(); + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +gcc_pure +static bool +skip_symlink(const Directory *directory, const char *utf8_name) +{ +#ifndef WIN32 + const Path path_fs = map_directory_child_fs(directory, utf8_name); + if (path_fs.IsNull()) + return true; + + const Path target = ReadLink(path_fs); + if (target.IsNull()) + /* 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; + } + + const char *target_str = target.c_str(); + + if (g_path_is_absolute(target_str)) { + /* if the symlink points to an absolute path, see if + that path is inside the music directory */ + const char *relative = map_to_relative_path(target_str); + return relative > target_str + ? !follow_inside_symlinks + : !follow_outside_symlinks; + } + + const char *p = target_str; + 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 +update_directory(Directory *directory, const struct stat *st) +{ + assert(S_ISDIR(st->st_mode)); + + directory_set_stat(directory, st); + + const Path path_fs = map_directory_fs(directory); + if (path_fs.IsNull()) + return false; + + DirectoryReader reader(path_fs); + if (reader.HasFailed()) { + int error = errno; + const auto path_utf8 = path_fs.ToUTF8(); + FormatErrno(update_domain, error, + "Failed to open directory %s", + path_utf8.c_str()); + return false; + } + + ExcludeList exclude_list; + exclude_list.LoadFile(Path::Build(path_fs, ".mpdignore")); + + if (!exclude_list.IsEmpty()) + remove_excluded_from_directory(directory, exclude_list); + + purge_deleted_from_directory(directory); + + while (reader.ReadEntry()) { + std::string utf8; + struct stat st2; + + const Path entry = reader.GetEntry(); + + if (skip_path(entry) || exclude_list.Check(entry)) + continue; + + utf8 = entry.ToUTF8(); + if (utf8.empty()) + continue; + + if (skip_symlink(directory, utf8.c_str())) { + modified |= delete_name_in(directory, utf8.c_str()); + continue; + } + + if (stat_directory_child(directory, utf8.c_str(), &st2) == 0) + update_directory_child(directory, utf8.c_str(), &st2); + else + modified |= delete_name_in(directory, utf8.c_str()); + } + + directory->mtime = st->st_mtime; + + return true; +} + +static Directory * +directory_make_child_checked(Directory *parent, const char *name_utf8) +{ + db_lock(); + Directory *directory = parent->FindChild(name_utf8); + db_unlock(); + + if (directory != NULL) + return directory; + + struct stat st; + if (stat_directory_child(parent, name_utf8, &st) < 0 || + find_inode_ancestor(parent, st.st_ino, st.st_dev)) + return NULL; + + if (skip_symlink(parent, name_utf8)) + return NULL; + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + db_lock(); + Song *conflicting = parent->FindSong(name_utf8); + if (conflicting) + delete_song(parent, conflicting); + + directory = parent->CreateChild(name_utf8); + db_unlock(); + + directory_set_stat(directory, &st); + return directory; +} + +static Directory * +directory_make_uri_parent_checked(const char *uri) +{ + Directory *directory = db_get_root(); + char *duplicated = g_strdup(uri); + char *name_utf8 = duplicated, *slash; + + while ((slash = strchr(name_utf8, '/')) != NULL) { + *slash = 0; + + if (*name_utf8 == 0) + continue; + + directory = directory_make_child_checked(directory, name_utf8); + if (directory == NULL) + break; + + name_utf8 = slash + 1; + } + + g_free(duplicated); + return directory; +} + +static void +update_uri(const char *uri) +{ + Directory *parent = directory_make_uri_parent_checked(uri); + if (parent == NULL) + return; + + char *name = g_path_get_basename(uri); + + struct stat st; + if (!skip_symlink(parent, name) && + stat_directory_child(parent, name, &st) == 0) + update_directory_child(parent, name, &st); + else + modified |= 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)) { + update_uri(path); + } else { + Directory *directory = db_get_root(); + struct stat st; + + if (stat_directory(directory, &st) == 0) + update_directory(directory, &st); + } + + return modified; +} diff --git a/src/UpdateWalk.hxx b/src/UpdateWalk.hxx new file mode 100644 index 000000000..62c0d0a8e --- /dev/null +++ b/src/UpdateWalk.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_WALK_HXX +#define MPD_UPDATE_WALK_HXX + +#include "check.h" + +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); + +#endif diff --git a/src/Volume.cxx b/src/Volume.cxx new file mode 100644 index 000000000..b23b9688f --- /dev/null +++ b/src/Volume.cxx @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Volume.hxx" +#include "MixerAll.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#define SW_VOLUME_STATE "sw_volume: " + +static constexpr Domain volume_domain("volume"); + +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; + +/** + * Handler for #GlobalEvents::MIXER. + */ +static void +mixer_event_callback(void) +{ + /* flush the hardware volume cache */ + last_hardware_volume = -1; + + /* notify clients */ + idle_add(IDLE_MIXER); +} + +void volume_finish(void) +{ + g_timer_destroy(hardware_volume_timer); +} + +void volume_init(void) +{ + hardware_volume_timer = g_timer_new(); + + GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); +} + +int volume_level_get(void) +{ + assert(hardware_volume_timer != NULL); + + if (last_hardware_volume >= 0 && + g_timer_elapsed(hardware_volume_timer, NULL) < 1.0) + /* throttle access to hardware mixers */ + return last_hardware_volume; + + last_hardware_volume = mixer_all_get_volume(); + g_timer_start(hardware_volume_timer); + return last_hardware_volume; +} + +static bool software_volume_change(unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + mixer_all_set_software_volume(volume); + + return true; +} + +static bool hardware_volume_change(unsigned volume) +{ + /* reset the cache */ + last_hardware_volume = -1; + + return mixer_all_set_volume(volume); +} + +bool volume_level_change(unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + + idle_add(IDLE_MIXER); + + return hardware_volume_change(volume); +} + +bool +read_sw_volume_state(const char *line) +{ + char *end = NULL; + long int sv; + + if (!g_str_has_prefix(line, SW_VOLUME_STATE)) + return false; + + line += sizeof(SW_VOLUME_STATE) - 1; + sv = strtol(line, &end, 10); + if (*end == 0 && sv >= 0 && sv <= 100) + software_volume_change(sv); + else + FormatWarning(volume_domain, + "Can't parse software volume: %s", line); + return true; +} + +void save_sw_volume_state(FILE *fp) +{ + 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.hxx b/src/Volume.hxx new file mode 100644 index 000000000..024b2840a --- /dev/null +++ b/src/Volume.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_VOLUME_HXX +#define MPD_VOLUME_HXX + +#include <stdio.h> + +void volume_init(void); + +void volume_finish(void); + +int volume_level_get(void); + +bool volume_level_change(unsigned volume); + +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/Win32Main.cxx b/src/Win32Main.cxx new file mode 100644 index 000000000..03be38db6 --- /dev/null +++ b/src/Win32Main.cxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Main.hxx" + +#ifdef WIN32 + +#include "gcc.h" +#include "GlobalEvents.hxx" +#include "system/FatalError.hxx" + +#include <cstdlib> +#include <atomic> + +#include <glib.h> + +#include <windows.h> + +static int service_argc; +static char **service_argv; +static char service_name[] = ""; +static std::atomic_bool running; +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, ¤t_status); +} + +static DWORD WINAPI +service_dispatcher(gcc_unused DWORD control, gcc_unused DWORD event_type, + gcc_unused void *event_data, gcc_unused void *context) +{ + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); + return NO_ERROR; + default: + return NO_ERROR; + } +} + +static void WINAPI +service_main(gcc_unused DWORD argc, gcc_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); + FormatFatalError("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 (running.load()) { + // Recent msdn docs that process is terminated + // if this function returns TRUE. + // We initiate correct shutdown sequence (if possible). + // Once main() returns CRT will terminate our process + // regardless our thread is still active. + // If this did not happen within 3 seconds + // let's shutdown anyway. + GlobalEvents::Emit(GlobalEvents::SHUTDOWN); + // Under debugger it's better to wait indefinitely + // to allow debugging of shutdown code. + Sleep(IsDebuggerPresent() ? INFINITE : 3000); + } + // If we're not running main loop there is no chance for + // clean shutdown. + std::exit(EXIT_FAILURE); + 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 */ + running.store(false); + SetConsoleTitle("Music Player Daemon"); + SetConsoleCtrlHandler(console_handler, TRUE); + return mpd_main(argc, argv); + } + + error_message = g_win32_error_message(error_code); + FormatFatalError("StartServiceCtrlDispatcher() failed: %s", + error_message); +} + +void win32_app_started() +{ + if (service_handle != 0) + service_notify_status(SERVICE_RUNNING); + else + running.store(true); +} + +void win32_app_stopping() +{ + if (service_handle != 0) + service_notify_status(SERVICE_STOP_PENDING); + else + running.store(false); +} + +#endif diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx new file mode 100644 index 000000000..9714b13b1 --- /dev/null +++ b/src/ZeroconfAvahi.cxx @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ZeroconfAvahi.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/Loop.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include <avahi-glib/glib-watch.h> + +static constexpr Domain avahi_domain("avahi"); + +static char *avahiName; +static int avahiRunning; +#ifndef USE_EPOLL +static AvahiGLibPoll *avahi_glib_poll; +#endif +static const AvahiPoll *avahi_poll; +static AvahiClient *avahiClient; +static AvahiEntryGroup *avahiGroup; + +static void avahiRegisterService(AvahiClient * c); + +/* Callback when the EntryGroup changes state */ +static void avahiGroupCallback(AvahiEntryGroup * g, + AvahiEntryGroupState state, + gcc_unused void *userdata) +{ + char *n; + assert(g); + + FormatDebug(avahi_domain, + "Service group changed to state %d", state); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + FormatInfo(avahi_domain, + "Service '%s' successfully established.", + avahiName); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + /* A service name collision happened. Let's pick a new name */ + n = avahi_alternative_service_name(avahiName); + avahi_free(avahiName); + avahiName = n; + + FormatInfo(avahi_domain, + "Service name collision, renaming service to '%s'", + avahiName); + + /* And recreate the services */ + avahiRegisterService(avahi_entry_group_get_client(g)); + break; + + case AVAHI_ENTRY_GROUP_FAILURE: + FormatError(avahi_domain, + "Entry group failure: %s", + avahi_strerror(avahi_client_errno + (avahi_entry_group_get_client(g)))); + /* Some kind of failure happened while we were registering our services */ + avahiRunning = 0; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + LogDebug(avahi_domain, "Service group is UNCOMMITED"); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + LogDebug(avahi_domain, "Service group is REGISTERING"); + } +} + +/* Registers a new service with avahi */ +static void avahiRegisterService(AvahiClient * c) +{ + FormatDebug(avahi_domain, "Registering service %s/%s", + SERVICE_TYPE, avahiName); + + int ret; + assert(c); + + /* If this is the first time we're called, + * let's create a new entry group */ + if (!avahiGroup) { + avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); + if (!avahiGroup) { + FormatError(avahi_domain, + "Failed to create avahi EntryGroup: %s", + avahi_strerror(avahi_client_errno(c))); + goto fail; + } + } + + /* Add the service */ + /* TODO: This currently binds to ALL interfaces. + * We could maybe add a service per actual bound interface, + * if that's better. */ + ret = avahi_entry_group_add_service(avahiGroup, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AvahiPublishFlags(0), + avahiName, SERVICE_TYPE, NULL, + NULL, listen_port, NULL); + if (ret < 0) { + FormatError(avahi_domain, "Failed to add service %s: %s", + SERVICE_TYPE, avahi_strerror(ret)); + goto fail; + } + + /* Tell the server to register the service group */ + ret = avahi_entry_group_commit(avahiGroup); + if (ret < 0) { + FormatError(avahi_domain, "Failed to commit service group: %s", + avahi_strerror(ret)); + goto fail; + } + return; + +fail: + avahiRunning = 0; +} + +/* Callback when avahi changes state */ +static void avahiClientCallback(AvahiClient * c, AvahiClientState state, + gcc_unused void *userdata) +{ + int reason; + assert(c); + + /* Called whenever the client or server state changes */ + FormatDebug(avahi_domain, "Client changed to state %d", state); + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + LogDebug(avahi_domain, "Client is RUNNING"); + + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if (!avahiGroup) + avahiRegisterService(c); + break; + + case AVAHI_CLIENT_FAILURE: + reason = avahi_client_errno(c); + if (reason == AVAHI_ERR_DISCONNECTED) { + LogInfo(avahi_domain, + "Client Disconnected, will reconnect shortly"); + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = NULL; + } + if (avahiClient) + avahi_client_free(avahiClient); + avahiClient = + avahi_client_new(avahi_poll, + AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, + &reason); + if (!avahiClient) { + FormatWarning(avahi_domain, + "Could not reconnect: %s", + avahi_strerror(reason)); + avahiRunning = 0; + } + } else { + FormatWarning(avahi_domain, + "Client failure: %s (terminal)", + avahi_strerror(reason)); + avahiRunning = 0; + } + break; + + case AVAHI_CLIENT_S_COLLISION: + LogDebug(avahi_domain, "Client is COLLISION"); + + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if (avahiGroup) { + LogDebug(avahi_domain, "Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_S_REGISTERING: + LogDebug(avahi_domain, "Client is REGISTERING"); + + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + + if (avahiGroup) { + LogDebug(avahi_domain, "Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_CONNECTING: + LogDebug(avahi_domain, "Client is CONNECTING"); + break; + } +} + +void +AvahiInit(EventLoop &loop, const char *serviceName) +{ + LogDebug(avahi_domain, "Initializing interface"); + + if (!avahi_is_valid_service_name(serviceName)) + FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName); + + avahiName = avahi_strdup(serviceName); + + avahiRunning = 1; + +#ifdef USE_EPOLL + // TODO + (void)loop; + if (1==1) return; +#else + avahi_glib_poll = avahi_glib_poll_new(loop.GetContext(), + G_PRIORITY_DEFAULT); + avahi_poll = avahi_glib_poll_get(avahi_glib_poll); +#endif + + int error; + avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &error); + + if (!avahiClient) { + FormatError(avahi_domain, "Failed to create client: %s", + avahi_strerror(error)); + AvahiDeinit(); + } +} + +void +AvahiDeinit(void) +{ + LogDebug(avahi_domain, "Shutting down interface"); + + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = NULL; + } + + if (avahiClient) { + avahi_client_free(avahiClient); + avahiClient = NULL; + } + +#ifdef USE_EPOLL + // TODO +#else + if (avahi_glib_poll != NULL) { + avahi_glib_poll_free(avahi_glib_poll); + avahi_glib_poll = NULL; + } +#endif + + avahi_free(avahiName); + avahiName = NULL; +} diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx new file mode 100644 index 000000000..bb046350a --- /dev/null +++ b/src/ZeroconfAvahi.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ZEROCONF_AVAHI_HXX +#define MPD_ZEROCONF_AVAHI_HXX + +class EventLoop; + +void +AvahiInit(EventLoop &loop, const char *service_name); + +void +AvahiDeinit(); + +#endif diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx new file mode 100644 index 000000000..215391b76 --- /dev/null +++ b/src/ZeroconfBonjour.cxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ZeroconfBonjour.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/SocketMonitor.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "gcc.h" + +#include <glib.h> + +#include <dns_sd.h> + +static constexpr Domain bonjour_domain("bonjour"); + +class BonjourMonitor final : public SocketMonitor { + DNSServiceRef service_ref; + +public: + BonjourMonitor(EventLoop &_loop, DNSServiceRef _service_ref) + :SocketMonitor(DNSServiceRefSockFD(_service_ref), _loop), + service_ref(_service_ref) { + ScheduleRead(); + } + + ~BonjourMonitor() { + Steal(); + DNSServiceRefDeallocate(service_ref); + } + +protected: + virtual bool OnSocketReady(gcc_unused unsigned flags) override { + DNSServiceProcessResult(service_ref); + return false; + } +}; + +static BonjourMonitor *bonjour_monitor; + +static void +dnsRegisterCallback(gcc_unused DNSServiceRef sdRef, + gcc_unused DNSServiceFlags flags, + DNSServiceErrorType errorCode, const char *name, + gcc_unused const char *regtype, + gcc_unused const char *domain, + gcc_unused void *context) +{ + if (errorCode != kDNSServiceErr_NoError) { + LogError(bonjour_domain, + "Failed to register zeroconf service"); + + bonjour_monitor->Cancel(); + } else { + FormatDebug(bonjour_domain, + "Registered zeroconf service with name '%s'", + name); + } +} + +void +BonjourInit(EventLoop &loop, const char *service_name) +{ + DNSServiceRef dnsReference; + DNSServiceErrorType error = DNSServiceRegister(&dnsReference, + 0, 0, service_name, + SERVICE_TYPE, NULL, NULL, + g_htons(listen_port), 0, + NULL, + dnsRegisterCallback, + NULL); + + if (error != kDNSServiceErr_NoError) { + LogError(bonjour_domain, + "Failed to register zeroconf service"); + + if (dnsReference) { + DNSServiceRefDeallocate(dnsReference); + dnsReference = NULL; + } + return; + } + + bonjour_monitor = new BonjourMonitor(loop, dnsReference); +} + +void +BonjourDeinit() +{ + delete bonjour_monitor; +} diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx new file mode 100644 index 000000000..d91fe9a0d --- /dev/null +++ b/src/ZeroconfBonjour.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ZEROCONF_BONJOUR_HXX +#define MPD_ZEROCONF_BONJOUR_HXX + +class EventLoop; + +void +BonjourInit(EventLoop &loop, const char *service_name); + +void +BonjourDeinit(); + +#endif diff --git a/src/ZeroconfGlue.cxx b/src/ZeroconfGlue.cxx new file mode 100644 index 000000000..44c257872 --- /dev/null +++ b/src/ZeroconfGlue.cxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ZeroconfGlue.hxx" +#include "ZeroconfAvahi.hxx" +#include "ZeroconfBonjour.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "Listen.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "gcc.h" + +static constexpr Domain zeroconf_domain("zeroconf"); + +/* The default service name to publish + * (overridden by 'zeroconf_name' config parameter) + */ +#define SERVICE_NAME "Music Player" + +#define DEFAULT_ZEROCONF_ENABLED 1 + +static int zeroconfEnabled; + +void +ZeroconfInit(gcc_unused EventLoop &loop) +{ + const char *serviceName; + + zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED, + DEFAULT_ZEROCONF_ENABLED); + if (!zeroconfEnabled) + return; + + if (listen_port <= 0) { + LogInfo(zeroconf_domain, + "No global port, disabling zeroconf"); + zeroconfEnabled = false; + return; + } + + serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); + +#ifdef HAVE_AVAHI + AvahiInit(loop, serviceName); +#endif + +#ifdef HAVE_BONJOUR + BonjourInit(loop, serviceName); +#endif +} + +void +ZeroconfDeinit() +{ + if (!zeroconfEnabled) + return; + +#ifdef HAVE_AVAHI + AvahiDeinit(); +#endif /* HAVE_AVAHI */ + +#ifdef HAVE_BONJOUR + BonjourDeinit(); +#endif +} diff --git a/src/ZeroconfGlue.hxx b/src/ZeroconfGlue.hxx new file mode 100644 index 000000000..2a291ce29 --- /dev/null +++ b/src/ZeroconfGlue.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ZEROCONF_GLUE_HXX +#define MPD_ZEROCONF_GLUE_HXX + +#include "check.h" + +class EventLoop; + +#ifdef HAVE_ZEROCONF + +void +ZeroconfInit(EventLoop &loop); + +void +ZeroconfDeinit(); + +#else /* ! HAVE_ZEROCONF */ + +static inline void +ZeroconfInit(EventLoop &) +{} + +static inline void +ZeroconfDeinit() +{} + +#endif /* ! HAVE_ZEROCONF */ + +#endif diff --git a/src/ZeroconfInternal.hxx b/src/ZeroconfInternal.hxx new file mode 100644 index 000000000..2eadcff6e --- /dev/null +++ b/src/ZeroconfInternal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef ZEROCONF_INTERNAL_H +#define ZEROCONF_INTERNAL_H + +/* The dns-sd service type qualifier to publish */ +#define SERVICE_TYPE "_mpd._tcp" + +#endif diff --git a/src/ack.h b/src/ack.h deleted file mode 100644 index 440bc27d8..000000000 --- a/src/ack.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ACK_H -#define MPD_ACK_H - -#include <glib.h> - -enum ack { - ACK_ERROR_NOT_LIST = 1, - ACK_ERROR_ARG = 2, - ACK_ERROR_PASSWORD = 3, - ACK_ERROR_PERMISSION = 4, - ACK_ERROR_UNKNOWN = 5, - - ACK_ERROR_NO_EXIST = 50, - ACK_ERROR_PLAYLIST_MAX = 51, - ACK_ERROR_SYSTEM = 52, - ACK_ERROR_PLAYLIST_LOAD = 53, - ACK_ERROR_UPDATE_ALREADY = 54, - ACK_ERROR_PLAYER_SYNC = 55, - ACK_ERROR_EXIST = 56, -}; - -/** - * Quark for GError.domain; the code is an enum #ack. - */ -G_GNUC_CONST -static inline GQuark -ack_quark(void) -{ - return g_quark_from_static_string("ack"); -} - -#endif diff --git a/src/aiff.c b/src/aiff.c deleted file mode 100644 index f66f29e2d..000000000 --- a/src/aiff.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "aiff.h" - -#include <glib.h> - -#include <stdint.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "aiff" - -struct aiff_header { - char id[4]; - uint32_t size; - char format[4]; -}; - -struct aiff_chunk_header { - char id[4]; - uint32_t size; -}; - -size_t -aiff_seek_id3(FILE *file) -{ - int ret; - struct stat st; - struct aiff_header header; - struct aiff_chunk_header chunk; - size_t size; - - /* determine the file size */ - - ret = fstat(fileno(file), &st); - if (ret < 0) { - g_warning("Failed to stat file descriptor: %s", - g_strerror(errno)); - return 0; - } - - /* seek to the beginning and read the AIFF header */ - - ret = fseek(file, 0, SEEK_SET); - if (ret != 0) { - g_warning("Failed to seek: %s", g_strerror(errno)); - return 0; - } - - size = fread(&header, sizeof(header), 1, file); - if (size != 1 || - memcmp(header.id, "FORM", 4) != 0 || - GUINT32_FROM_BE(header.size) > (uint32_t)st.st_size || - (memcmp(header.format, "AIFF", 4) != 0 && - memcmp(header.format, "AIFC", 4) != 0)) - /* not a AIFF file */ - return 0; - - while (true) { - /* read the chunk header */ - - size = fread(&chunk, sizeof(chunk), 1, file); - if (size != 1) - return 0; - - size = GUINT32_FROM_BE(chunk.size); - if (size > G_MAXINT32) - /* too dangerous, bail out: possible integer - underflow when casting to off_t */ - return 0; - - if (size % 2 != 0) - /* pad byte */ - ++size; - - if (memcmp(chunk.id, "ID3 ", 4) == 0) - /* found it! */ - return size; - - ret = fseek(file, size, SEEK_CUR); - if (ret != 0) - return 0; - } -} diff --git a/src/aiff.h b/src/aiff.h deleted file mode 100644 index a0ae2d41a..000000000 --- a/src/aiff.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A parser for the AIFF file format. - */ - -#ifndef MPD_AIFF_H -#define MPD_AIFF_H - -#include <stdbool.h> -#include <stddef.h> -#include <stdio.h> - -/** - * Seeks the AIFF file to the ID3 chunk. - * - * @return the size of the ID3 chunk on success, or 0 if this is not a - * AIFF file or no ID3 chunk was found - */ -size_t -aiff_seek_id3(FILE *file); - -#endif diff --git a/src/ape.c b/src/ape.c deleted file mode 100644 index 6257fe6b3..000000000 --- a/src/ape.c +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ape.h" - -#include <glib.h> - -#include <stdint.h> -#include <assert.h> -#include <stdio.h> -#include <string.h> - -struct ape_footer { - unsigned char id[8]; - uint32_t version; - uint32_t length; - uint32_t count; - unsigned char flags[4]; - unsigned char reserved[8]; -}; - -static bool -ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx) -{ - /* determine if file has an apeV2 tag */ - struct ape_footer footer; - if (fseek(fp, -(long)sizeof(footer), SEEK_END) || - fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) || - memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 || - GUINT32_FROM_LE(footer.version) != 2000) - return false; - - /* find beginning of ape tag */ - size_t remaining = GUINT32_FROM_LE(footer.length); - if (remaining <= sizeof(footer) + 10 || - /* refuse to load more than one megabyte of tag data */ - remaining > 1024 * 1024 || - fseek(fp, -(long)remaining, SEEK_END)) - return false; - - /* read tag into buffer */ - remaining -= sizeof(footer); - assert(remaining > 10); - - char *buffer = g_malloc(remaining); - if (fread(buffer, 1, remaining, fp) != remaining) { - g_free(buffer); - return false; - } - - /* read tags */ - unsigned n = GUINT32_FROM_LE(footer.count); - const char *p = buffer; - while (n-- && remaining > 10) { - size_t size = GUINT32_FROM_LE(*(const uint32_t *)p); - p += 4; - remaining -= 4; - unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p); - p += 4; - remaining -= 4; - - /* get the key */ - const char *key = p; - while (remaining > size && *p != '\0') { - p++; - remaining--; - } - p++; - remaining--; - - /* get the value */ - if (remaining < size) - break; - - if (!callback(flags, key, p, size, ctx)) - break; - - p += size; - remaining -= size; - } - - g_free(buffer); - return true; -} - -bool -tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx) -{ - FILE *fp; - - fp = fopen(path_fs, "rb"); - if (fp == NULL) - return false; - - bool success = ape_scan_internal(fp, callback, ctx); - fclose(fp); - return success; -} diff --git a/src/ape.h b/src/ape.h deleted file mode 100644 index c2b271b15..000000000 --- a/src/ape.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx new file mode 100644 index 000000000..45cdec87c --- /dev/null +++ b/src/archive/Bzip2ArchivePlugin.cxx @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Bzip2ArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#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 + +class Bzip2ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + char *const name; + struct input_stream *const istream; + + Bzip2ArchiveFile(const char *path, input_stream *_is) + :ArchiveFile(bz2_archive_plugin), + name(g_path_get_basename(path)), + istream(_is) { + // remove .bz2 suffix + size_t len = strlen(name); + if (len > 4) + name[len - 4] = 0; + } + + ~Bzip2ArchiveFile() { + istream->Close(); + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (!ref.Decrement()) + return; + + g_free(name); + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override { + visitor.VisitArchiveEntry(name); + } + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +struct Bzip2InputStream { + struct input_stream base; + + Bzip2ArchiveFile *archive; + + bool eof; + + bz_stream bzstream; + + char buffer[5000]; + + Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri, + Mutex &mutex, Cond &cond); + ~Bzip2InputStream(); + + bool Open(Error &error); + void Close(); +}; + +extern const struct input_plugin bz2_inputplugin; + +static constexpr Domain bz2_domain("bz2"); + +static inline GQuark +bz2_quark(void) +{ + return g_quark_from_static_string("bz2"); +} + +/* single archive handling allocation helpers */ + +inline bool +Bzip2InputStream::Open(Error &error) +{ + bzstream.bzalloc = nullptr; + bzstream.bzfree = nullptr; + bzstream.opaque = nullptr; + + bzstream.next_in = (char *)buffer; + bzstream.avail_in = 0; + + int ret = BZ2_bzDecompressInit(&bzstream, 0, 0); + if (ret != BZ_OK) { + error.Set(bz2_domain, ret, + "BZ2_bzDecompressInit() has failed"); + return false; + } + + base.ready = true; + return true; +} + +inline void +Bzip2InputStream::Close() +{ + BZ2_bzDecompressEnd(&bzstream); +} + +/* archive open && listing routine */ + +static ArchiveFile * +bz2_open(const char *pathname, Error &error) +{ + static Mutex mutex; + static Cond cond; + input_stream *is = input_stream::Open(pathname, mutex, cond, error); + if (is == nullptr) + return nullptr; + + return new Bzip2ArchiveFile(pathname, is); +} + +/* single archive handling */ + +Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context, const char *uri, + Mutex &mutex, Cond &cond) + :base(bz2_inputplugin, uri, mutex, cond), + archive(&_context), eof(false) +{ + archive->Ref(); +} + +Bzip2InputStream::~Bzip2InputStream() +{ + archive->Unref(); +} + +input_stream * +Bzip2ArchiveFile::OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) +{ + Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond); + if (!bis->Open(error)) { + delete bis; + return NULL; + } + + return &bis->base; +} + +static void +bz2_is_close(struct input_stream *is) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + bis->Close(); + delete bis; +} + +static bool +bz2_fillbuffer(Bzip2InputStream *bis, Error &error) +{ + size_t count; + bz_stream *bzstream; + + bzstream = &bis->bzstream; + + if (bzstream->avail_in > 0) + return true; + + count = bis->archive->istream->Read(bis->buffer, sizeof(bis->buffer), + error); + 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, + Error &error) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + bz_stream *bzstream; + int bz_result; + size_t nbytes = 0; + + if (bis->eof) + return 0; + + bzstream = &bis->bzstream; + bzstream->next_out = (char *)ptr; + bzstream->avail_out = length; + + do { + if (!bz2_fillbuffer(bis, error)) + return 0; + + bz_result = BZ2_bzDecompress(bzstream); + + if (bz_result == BZ_STREAM_END) { + bis->eof = true; + break; + } + + if (bz_result != BZ_OK) { + error.Set(bz2_domain, 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) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + return bis->eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + NULL +}; + +const struct input_plugin bz2_inputplugin = { + nullptr, + nullptr, + nullptr, + nullptr, + bz2_is_close, + nullptr, + nullptr, + nullptr, + nullptr, + bz2_is_read, + bz2_is_eof, + nullptr, +}; + +const struct archive_plugin bz2_archive_plugin = { + "bz2", + nullptr, + nullptr, + bz2_open, + bz2_extensions, +}; + diff --git a/src/archive/Bzip2ArchivePlugin.hxx b/src/archive/Bzip2ArchivePlugin.hxx new file mode 100644 index 000000000..a7933a7a7 --- /dev/null +++ b/src/archive/Bzip2ArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_BZ2_HXX +#define MPD_ARCHIVE_BZ2_HXX + +extern const struct archive_plugin bz2_archive_plugin; + +#endif diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx new file mode 100644 index 000000000..2fee25311 --- /dev/null +++ b/src/archive/Iso9660ArchivePlugin.cxx @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Iso9660ArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <cdio/cdio.h> +#include <cdio/iso9660.h> + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +class Iso9660ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + iso9660_t *iso; + + Iso9660ArchiveFile(iso9660_t *_iso) + :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} + + ~Iso9660ArchiveFile() { + iso9660_close(iso); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + void Visit(const char *path, ArchiveVisitor &visitor); + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +extern const struct input_plugin iso9660_input_plugin; + +static constexpr Domain iso9660_domain("iso9660"); + +/* archive open && listing routine */ + +inline void +Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor) +{ + 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 (iso9660_stat_s::_STAT_DIR == statbuf->type ) { + if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { + strcat(pathname, "/"); + Visit(pathname, visitor); + } + } else { + //remove leading / + visitor.VisitArchiveEntry(pathname + 1); + } + } + _cdio_list_free (entlist, true); +} + +static ArchiveFile * +iso9660_archive_open(const char *pathname, Error &error) +{ + /* open archive */ + auto iso = iso9660_open(pathname); + if (iso == nullptr) { + error.Format(iso9660_domain, + "Failed to open ISO9660 file %s", pathname); + return NULL; + } + + return new Iso9660ArchiveFile(iso); +} + +void +Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) +{ + Visit("/", visitor); +} + +/* single archive handling */ + +struct Iso9660InputStream { + struct input_stream base; + + Iso9660ArchiveFile *archive; + + iso9660_stat_t *statbuf; + size_t max_blocks; + + Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *uri, + Mutex &mutex, Cond &cond, + iso9660_stat_t *_statbuf) + :base(iso9660_input_plugin, uri, mutex, cond), + archive(&_archive), statbuf(_statbuf), + max_blocks(CEILING(statbuf->size, ISO_BLOCKSIZE)) { + + base.ready = true; + base.size = statbuf->size; + + archive->ref.Increment(); + } + + ~Iso9660InputStream() { + free(statbuf); + archive->Unref(); + } +}; + +input_stream * +Iso9660ArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + auto statbuf = iso9660_ifs_stat_translate(iso, pathname); + if (statbuf == nullptr) { + error.Format(iso9660_domain, + "not found in the ISO file: %s", pathname); + return NULL; + } + + Iso9660InputStream *iis = + new Iso9660InputStream(*this, pathname, mutex, cond, + statbuf); + return &iis->base; +} + +static void +iso9660_input_close(struct input_stream *is) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + + delete iis; +} + + +static size_t +iso9660_input_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)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) { + error.Format(iso9660_domain, + "error reading ISO file at lsn %lu", + (unsigned long)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 +}; + +const struct input_plugin iso9660_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + iso9660_input_close, + nullptr, + nullptr, + nullptr, + nullptr, + iso9660_input_read, + iso9660_input_eof, + nullptr, +}; + +const struct archive_plugin iso9660_archive_plugin = { + "iso", + nullptr, + nullptr, + iso9660_archive_open, + iso9660_archive_extensions, +}; diff --git a/src/archive/Iso9660ArchivePlugin.hxx b/src/archive/Iso9660ArchivePlugin.hxx new file mode 100644 index 000000000..6fbab6159 --- /dev/null +++ b/src/archive/Iso9660ArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_ARCHIVE_ISO9660_HXX + +extern const struct archive_plugin iso9660_archive_plugin; + +#endif diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx new file mode 100644 index 000000000..de2c62b5b --- /dev/null +++ b/src/archive/ZzipArchivePlugin.cxx @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ZzipArchivePlugin.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <zzip/zzip.h> + +#include <string.h> + +class ZzipArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + ZZIP_DIR *const dir; + + ZzipArchiveFile(ZZIP_DIR *_dir) + :ArchiveFile(zzip_archive_plugin), dir(_dir) {} + + ~ZzipArchiveFile() { + zzip_dir_close(dir); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual input_stream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +extern const struct input_plugin zzip_input_plugin; + +static constexpr Domain zzip_domain("zzip"); + +/* archive open && listing routine */ + +static ArchiveFile * +zzip_archive_open(const char *pathname, Error &error) +{ + ZZIP_DIR *dir = zzip_dir_open(pathname, NULL); + if (dir == nullptr) { + error.Format(zzip_domain, "Failed to open ZIP file %s", + pathname); + return NULL; + } + + return new ZzipArchiveFile(dir); +} + +inline void +ZzipArchiveFile::Visit(ArchiveVisitor &visitor) +{ + zzip_rewinddir(dir); + + ZZIP_DIRENT dirent; + while (zzip_dir_read(dir, &dirent)) + //add only files + if (dirent.st_size > 0) + visitor.VisitArchiveEntry(dirent.d_name); +} + +/* single archive handling */ + +struct ZzipInputStream { + struct input_stream base; + + ZzipArchiveFile *archive; + + ZZIP_FILE *file; + + ZzipInputStream(ZzipArchiveFile &_archive, const char *uri, + Mutex &mutex, Cond &cond, + ZZIP_FILE *_file) + :base(zzip_input_plugin, uri, mutex, cond), + archive(&_archive), file(_file) { + base.ready = true; + //we are seekable (but its not recommendent to do so) + base.seekable = true; + + ZZIP_STAT z_stat; + zzip_file_stat(file, &z_stat); + base.size = z_stat.st_size; + + archive->ref.Increment(); + } + + ~ZzipInputStream() { + zzip_file_close(file); + archive->Unref(); + } +}; + +input_stream * +ZzipArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0); + if (_file == nullptr) { + error.Format(zzip_domain, "not found in the ZIP file: %s", + pathname); + return NULL; + } + + ZzipInputStream *zis = + new ZzipInputStream(*this, pathname, + mutex, cond, + _file); + return &zis->base; +} + +static void +zzip_input_close(struct input_stream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + delete zis; +} + +static size_t +zzip_input_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + int ret; + + ret = zzip_file_read(zis->file, ptr, size); + if (ret < 0) { + error.Set(zzip_domain, "zzip_file_read() has failed"); + return 0; + } + + is->offset = zzip_tell(zis->file); + + return ret; +} + +static bool +zzip_input_eof(struct input_stream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + return (goffset)zzip_tell(zis->file) == is->size; +} + +static bool +zzip_input_seek(struct input_stream *is, + goffset offset, int whence, Error &error) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + zzip_off_t ofs = zzip_seek(zis->file, offset, whence); + if (ofs != -1) { + error.Set(zzip_domain, "zzip_seek() has failed"); + is->offset = ofs; + return true; + } + return false; +} + +/* exported structures */ + +static const char *const zzip_archive_extensions[] = { + "zip", + NULL +}; + +const struct input_plugin zzip_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + zzip_input_close, + nullptr, + nullptr, + nullptr, + nullptr, + zzip_input_read, + zzip_input_eof, + zzip_input_seek, +}; + +const struct archive_plugin zzip_archive_plugin = { + "zzip", + nullptr, + nullptr, + zzip_archive_open, + zzip_archive_extensions, +}; diff --git a/src/archive/ZzipArchivePlugin.hxx b/src/archive/ZzipArchivePlugin.hxx new file mode 100644 index 000000000..4ba16849b --- /dev/null +++ b/src/archive/ZzipArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_ARCHIVE_ZZIP_HXX + +extern const struct archive_plugin zzip_archive_plugin; + +#endif diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c deleted file mode 100644 index e2420048b..000000000 --- a/src/archive/bz2_archive_plugin.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * single bz2 archive handling (requires libbz2) - */ - -#include "config.h" -#include "archive/bz2_archive_plugin.h" -#include "archive_api.h" -#include "input_internal.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 */ - -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -static struct archive_file * -bz2_open(const char *pathname, GError **error_r) -{ - 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 - static GStaticMutex mutex = G_STATIC_MUTEX_INIT; - context->istream = input_stream_open(pathname, - g_static_mutex_get_mutex(&mutex), - NULL, - error_r); - if (context->istream == NULL) { - g_free(context); - return NULL; - } - - 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, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct bz2_archive_file *context = (struct bz2_archive_file *) file; - struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1); - - input_stream_init(&bis->base, &bz2_inputplugin, path, - mutex, cond); - - 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/archive/bz2_archive_plugin.h b/src/archive/bz2_archive_plugin.h deleted file mode 100644 index 46c69a66c..000000000 --- a/src/archive/bz2_archive_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_BZ2_H -#define MPD_ARCHIVE_BZ2_H - -extern const struct archive_plugin bz2_archive_plugin; - -#endif diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c deleted file mode 100644 index bb6cb9588..000000000 --- a/src/archive/iso9660_archive_plugin.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * iso archive handling (requires cdio, and iso9660) - */ - -#include "config.h" -#include "archive/iso9660_archive_plugin.h" -#include "archive_api.h" -#include "input_internal.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, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct iso9660_archive_file *context = - (struct iso9660_archive_file *)file; - struct iso9660_input_stream *iis; - - iis = g_new(struct iso9660_input_stream, 1); - input_stream_init(&iis->base, &iso9660_input_plugin, pathname, - mutex, cond); - - 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 deleted file mode 100644 index 47dc6e474..000000000 --- a/src/archive/iso9660_archive_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_ISO9660_H -#define MPD_ARCHIVE_ISO9660_H - -extern const struct archive_plugin iso9660_archive_plugin; - -#endif diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c deleted file mode 100644 index ad96b5f89..000000000 --- a/src/archive/zzip_archive_plugin.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * zip archive handling (requires zziplib) - */ - -#include "config.h" -#include "archive/zzip_archive_plugin.h" -#include "archive_api.h" -#include "archive_api.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#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, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct zzip_archive *context = (struct zzip_archive *) file; - struct zzip_input_stream *zis; - ZZIP_STAT z_stat; - - zis = g_new(struct zzip_input_stream, 1); - input_stream_init(&zis->base, &zzip_input_plugin, pathname, - mutex, cond); - - 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 deleted file mode 100644 index 2b2c01e5a..000000000 --- a/src/archive/zzip_archive_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index be3c35f7e..000000000 --- a/src/archive_api.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "archive_api.h" - -#include <stdio.h> - -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <glib.h> - -/** - * - * archive_lookup is used to determine if part of pathname refers to an regular - * file (archive). If so then its also used to split pathname into archive file - * and path used to locate file in archive. It also returns suffix of the file. - * How it works: - * We do stat of the parent of input pathname as long as we find an regular file - * Normally this should never happen. When routine returns true pathname modified - * and split into archive, inpath and suffix. Otherwise nothing happens - * - * For example: - * - * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3 - * is split into archive: /music/path/Talco.zip - * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3 - * and suffix: zip - */ - -bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix) -{ - char *pathdupe; - int len, idx; - struct stat st_info; - bool ret = false; - - *archive = NULL; - *inpath = NULL; - *suffix = NULL; - - pathdupe = g_strdup(pathname); - len = idx = strlen(pathname); - - while (idx > 0) { - //try to stat if its real directory - if (stat(pathdupe, &st_info) == -1) { - if (errno != ENOTDIR) { - g_warning("stat %s failed (errno=%d)\n", pathdupe, errno); - break; - } - } else { - //is something found ins original path (is not an archive) - if (idx == len) { - break; - } - //its a file ? - if (S_ISREG(st_info.st_mode)) { - //so the upper should be file - pathname[idx] = 0; - ret = true; - *archive = pathname; - *inpath = pathname + idx+1; - - //try to get suffix - *suffix = NULL; - while (idx > 0) { - if (pathname[idx] == '.') { - *suffix = pathname + idx + 1; - break; - } - idx--; - } - break; - } else { - g_warning("not a regular file %s\n", pathdupe); - break; - } - } - //find one dir up - while (idx > 0) { - if (pathdupe[idx] == '/') { - pathdupe[idx] = 0; - break; - } - idx--; - } - } - g_free(pathdupe); - return ret; -} - diff --git a/src/archive_api.h b/src/archive_api.h deleted file mode 100644 index 4e0f603f5..000000000 --- a/src/archive_api.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_API_H -#define MPD_ARCHIVE_API_H - -/* - * This is the public API which is used by archive plugins to - * provide transparent archive decompression layer for mpd - * - */ - -#include "archive_internal.h" -#include "archive_plugin.h" -#include "input_stream.h" - -#include <stdbool.h> - -bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); - -#endif - diff --git a/src/archive_internal.h b/src/archive_internal.h deleted file mode 100644 index 0d885e91c..000000000 --- a/src/archive_internal.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_INTERNAL_H -#define MPD_ARCHIVE_INTERNAL_H - -struct archive_file { - 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 deleted file mode 100644 index e23567bdb..000000000 --- a/src/archive_list.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "archive_list.h" -#include "archive_plugin.h" -#include "string_util.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> - -const struct archive_plugin *const archive_plugins[] = { -#ifdef HAVE_BZ2 - &bz2_archive_plugin, -#endif -#ifdef HAVE_ZZIP - &zzip_archive_plugin, -#endif -#ifdef HAVE_ISO9660 - &iso9660_archive_plugin, -#endif - NULL -}; - -/** which plugins have been initialized successfully? */ -static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1]; - -#define archive_plugins_for_each_enabled(plugin) \ - archive_plugins_for_each(plugin) \ - if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins]) - -const struct archive_plugin * -archive_plugin_from_suffix(const char *suffix) -{ - if (suffix == NULL) - return NULL; - - archive_plugins_for_each_enabled(plugin) - if (plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix)) - return plugin; - - return NULL; -} - -const struct archive_plugin * -archive_plugin_from_name(const char *name) -{ - archive_plugins_for_each_enabled(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} - -void archive_plugin_init_all(void) -{ - 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; - } -} - -void archive_plugin_deinit_all(void) -{ - archive_plugins_for_each_enabled(plugin) - if (plugin->finish != NULL) - plugin->finish(); -} - diff --git a/src/archive_list.h b/src/archive_list.h deleted file mode 100644 index f944583ed..000000000 --- a/src/archive_list.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_LIST_H -#define MPD_ARCHIVE_LIST_H - -struct archive_plugin; - -extern const struct archive_plugin *const archive_plugins[]; - -#define archive_plugins_for_each(plugin) \ - for (const struct archive_plugin *plugin, \ - *const*archive_plugin_iterator = &archive_plugins[0]; \ - (plugin = *archive_plugin_iterator) != NULL; \ - ++archive_plugin_iterator) - -/* interface for using plugins */ - -const struct archive_plugin * -archive_plugin_from_suffix(const char *suffix); - -const struct archive_plugin * -archive_plugin_from_name(const char *name); - -/* this is where we "load" all the "plugins" ;-) */ -void archive_plugin_init_all(void); - -/* this is where we "unload" all the "plugins" */ -void archive_plugin_deinit_all(void); - -#endif diff --git a/src/archive_plugin.c b/src/archive_plugin.c deleted file mode 100644 index cf23e6393..000000000 --- a/src/archive_plugin.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "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, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - assert(file != NULL); - assert(file->plugin != NULL); - assert(file->plugin->open_stream != NULL); - - return file->plugin->open_stream(file, path, mutex, cond, - error_r); -} diff --git a/src/archive_plugin.h b/src/archive_plugin.h deleted file mode 100644 index b7b92446d..000000000 --- a/src/archive_plugin.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 occurring, or - * NULL to ignore errors - */ - struct input_stream *(*open_stream)(struct archive_file *af, - const char *path, - GMutex *mutex, GCond *cond, - GError **error_r); - - /** - * 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, - GMutex *mutex, GCond *cond, - GError **error_r); - -#endif diff --git a/src/audio_check.c b/src/audio_check.c deleted file mode 100644 index a9aa2dd82..000000000 --- a/src/audio_check.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "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 deleted file mode 100644 index 9f71cf9c0..000000000 --- a/src/audio_check.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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. - */ -G_GNUC_CONST -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_config.c b/src/audio_config.c deleted file mode 100644 index 72869c384..000000000 --- a/src/audio_config.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "audio_config.h" -#include "audio_format.h" -#include "audio_parser.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "output_all.h" -#include "conf.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -static struct audio_format configured_audio_format; - -void getOutputAudioFormat(const struct audio_format *inAudioFormat, - struct audio_format *outAudioFormat) -{ - *outAudioFormat = *inAudioFormat; - audio_format_mask_apply(outAudioFormat, &configured_audio_format); -} - -void initAudioConfig(void) -{ - const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT); - GError *error = NULL; - bool ret; - - if (param == NULL) - return; - - ret = audio_format_parse(&configured_audio_format, param->value, - true, &error); - if (!ret) - MPD_ERROR("error parsing \"%s\" at line %i: %s", - CONF_AUDIO_OUTPUT_FORMAT, param->line, - error->message); -} diff --git a/src/audio_config.h b/src/audio_config.h deleted file mode 100644 index 85143247f..000000000 --- a/src/audio_config.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AUDIO_CONFIG_H -#define MPD_AUDIO_CONFIG_H - -#include <stdbool.h> - -struct audio_format; - -void getOutputAudioFormat(const struct audio_format *inFormat, - struct audio_format *outFormat); - -/* make sure initPlayerData is called before this function!! */ -void initAudioConfig(void); - -#endif diff --git a/src/audio_format.c b/src/audio_format.c deleted file mode 100644 index 45d94a853..000000000 --- a/src/audio_format.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "audio_format.h" - -#include <assert.h> -#include <stdio.h> - -void -audio_format_mask_apply(struct audio_format *af, - const struct audio_format *mask) -{ - assert(audio_format_valid(af)); - assert(audio_format_mask_valid(mask)); - - if (mask->sample_rate != 0) - af->sample_rate = mask->sample_rate; - - if (mask->format != SAMPLE_FORMAT_UNDEFINED) - af->format = mask->format; - - if (mask->channels != 0) - af->channels = mask->channels; - - assert(audio_format_valid(af)); -} - -const char * -sample_format_to_string(enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - return "?"; - - case SAMPLE_FORMAT_S8: - return "8"; - - case SAMPLE_FORMAT_S16: - return "16"; - - case SAMPLE_FORMAT_S24_P32: - return "24"; - - case SAMPLE_FORMAT_S32: - return "32"; - - case SAMPLE_FORMAT_FLOAT: - return "f"; - - case SAMPLE_FORMAT_DSD: - return "dsd"; - } - - /* unreachable */ - 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:%u", - af->sample_rate, sample_format_to_string(af->format), - af->channels); - - return s->buffer; -} diff --git a/src/audio_format.h b/src/audio_format.h deleted file mode 100644 index bf77add3b..000000000 --- a/src/audio_format.h +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AUDIO_FORMAT_H -#define MPD_AUDIO_FORMAT_H - -#include <glib.h> -#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, packed in 32 bit integers - * (the most significant byte is filled with the sign bit). - */ - SAMPLE_FORMAT_S24_P32, - - SAMPLE_FORMAT_S32, - - /** - * 32 bit floating point samples in the host's format. The - * range is -1.0f to +1.0f. - */ - SAMPLE_FORMAT_FLOAT, - - /** - * Direct Stream Digital. 1-bit samples; each frame has one - * byte (8 samples) per channel. - */ - SAMPLE_FORMAT_DSD, -}; - -static const unsigned MAX_CHANNELS = 8; - -/** - * This structure describes the format of a raw PCM stream. - */ -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; - - /** - * 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; -}; - -/** - * 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->format = SAMPLE_FORMAT_UNDEFINED; - af->channels = 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; -} - -/** - * 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 - */ -static inline bool -audio_valid_sample_rate(unsigned sample_rate) -{ - return sample_rate > 0 && sample_rate < (1 << 30); -} - -/** - * Checks whether the sample format is valid. - * - * @param bits the number of significant bits per sample - */ -static inline bool -audio_valid_sample_format(enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_S8: - case SAMPLE_FORMAT_S16: - case SAMPLE_FORMAT_S24_P32: - case SAMPLE_FORMAT_S32: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - return true; - - case SAMPLE_FORMAT_UNDEFINED: - break; - } - - return false; -} - -/** - * Checks whether the number of channels is valid. - */ -static inline bool -audio_valid_channel_count(unsigned channels) -{ - return channels >= 1 && channels <= MAX_CHANNELS; -} - -/** - * Returns false if the format is not valid for playback with MPD. - * This function performs some basic validity checks. - */ -G_GNUC_PURE -static inline bool audio_format_valid(const struct audio_format *af) -{ - return audio_valid_sample_rate(af->sample_rate) && - 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. - */ -G_GNUC_PURE -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->format == b->format && - a->channels == b->channels; -} - -void -audio_format_mask_apply(struct audio_format *af, - const struct audio_format *mask); - -G_GNUC_CONST -static inline unsigned -sample_format_size(enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_S8: - return 1; - - case SAMPLE_FORMAT_S16: - return 2; - - case SAMPLE_FORMAT_S24_P32: - case SAMPLE_FORMAT_S32: - case SAMPLE_FORMAT_FLOAT: - return 4; - - case SAMPLE_FORMAT_DSD: - /* each frame has 8 samples per channel */ - return 1; - - case SAMPLE_FORMAT_UNDEFINED: - return 0; - } - - assert(false); - return 0; -} - -/** - * Returns the size of each (mono) sample in bytes. - */ -G_GNUC_PURE -static inline unsigned audio_format_sample_size(const struct audio_format *af) -{ - return sample_format_size((enum sample_format)af->format); -} - -/** - * Returns the size of each full frame in bytes. - */ -G_GNUC_PURE -static inline unsigned -audio_format_frame_size(const struct audio_format *af) -{ - return audio_format_sample_size(af) * af->channels; -} - -/** - * Returns the floating point factor which converts a time span to a - * storage size in bytes. - */ -G_GNUC_PURE -static inline double audio_format_time_to_size(const struct audio_format *af) -{ - return af->sample_rate * audio_format_frame_size(af); -} - -/** - * 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 - */ -G_GNUC_PURE G_GNUC_MALLOC -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 - */ -G_GNUC_PURE G_GNUC_MALLOC -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 deleted file mode 100644 index 152eab5d4..000000000 --- a/src/audio_parser.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Parser functions for audio related objects. - * - */ - -#include "config.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "audio_check.h" -#include "gcc.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -/** - * The GLib quark used for errors reported by this library. - */ -static inline GQuark -audio_parser_quark(void) -{ - return g_quark_from_static_string("audio_parser"); -} - -static bool -parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r, - const char **endptr_r, GError **error_r) -{ - unsigned long value; - char *endptr; - - 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_r, audio_parser_quark(), 0, - "Failed to parse the sample rate"); - return false; - } 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; - } - - if (*src == 'f') { - *sample_format_r = SAMPLE_FORMAT_FLOAT; - *endptr_r = src + 1; - return true; - } - - if (memcmp(src, "dsd", 3) == 0) { - *sample_format_r = SAMPLE_FORMAT_DSD; - *endptr_r = src + 3; - return true; - } - - value = strtoul(src, &endptr, 10); - if (endptr == src) { - g_set_error(error_r, audio_parser_quark(), 0, - "Failed to parse the sample format"); - return false; - } - - 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) - /* for backwards compatibility */ - endptr += 2; - - 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; - } - - assert(audio_valid_sample_format(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; - } - - value = strtoul(src, &endptr, 10); - if (endptr == src) { - g_set_error(error_r, audio_parser_quark(), 0, - "Failed to parse the channel count"); - return false; - } else if (!audio_check_channel_count(value, error_r)) - return false; - - *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 GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - rate = 0; -#endif - - 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; - } - - /* parse sample format */ - -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - sample_format = SAMPLE_FORMAT_UNDEFINED; -#endif - - 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 */ - - 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; - } - - 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 deleted file mode 100644 index a963eb467..000000000 --- a/src/audio_parser.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Parser functions for audio related objects. - */ - -#ifndef AUDIO_PARSER_H -#define AUDIO_PARSER_H - -#include <glib.h> - -#include <stdbool.h> - -struct audio_format; - -/** - * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an - * #audio_format. - * - * @param dest the destination #audio_format struct - * @param src the input string - * @param mask if true, then "*" is allowed for any number of items - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success - */ -bool -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 deleted file mode 100644 index 559f39a9a..000000000 --- a/src/buffer.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "buffer.h" -#include "chunk.h" -#include "poison.h" - -#include <glib.h> - -#include <assert.h> - -struct music_buffer { - struct music_chunk *chunks; - unsigned num_chunks; - - struct music_chunk *available; - - /** a mutex which protects #available */ - GMutex *mutex; - -#ifndef NDEBUG - unsigned num_allocated; -#endif -}; - -struct music_buffer * -music_buffer_new(unsigned num_chunks) -{ - struct music_buffer *buffer; - struct music_chunk *chunk; - - assert(num_chunks > 0); - - buffer = g_new(struct music_buffer, 1); - - buffer->chunks = g_new(struct music_chunk, num_chunks); - buffer->num_chunks = num_chunks; - - chunk = buffer->available = buffer->chunks; - poison_undefined(chunk, sizeof(*chunk)); - - for (unsigned i = 1; i < num_chunks; ++i) { - chunk->next = &buffer->chunks[i]; - chunk = chunk->next; - poison_undefined(chunk, sizeof(*chunk)); - } - - chunk->next = NULL; - - buffer->mutex = g_mutex_new(); - -#ifndef NDEBUG - buffer->num_allocated = 0; -#endif - - return buffer; -} - -void -music_buffer_free(struct music_buffer *buffer) -{ - assert(buffer->chunks != NULL); - assert(buffer->num_chunks > 0); - assert(buffer->num_allocated == 0); - - g_mutex_free(buffer->mutex); - g_free(buffer->chunks); - g_free(buffer); -} - -unsigned -music_buffer_size(const struct music_buffer *buffer) -{ - return buffer->num_chunks; -} - -struct music_chunk * -music_buffer_allocate(struct music_buffer *buffer) -{ - struct music_chunk *chunk; - - g_mutex_lock(buffer->mutex); - - chunk = buffer->available; - if (chunk != NULL) { - buffer->available = chunk->next; - music_chunk_init(chunk); - -#ifndef NDEBUG - ++buffer->num_allocated; -#endif - } - - g_mutex_unlock(buffer->mutex); - return chunk; -} - -void -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); - poison_undefined(chunk, sizeof(*chunk)); - - chunk->next = buffer->available; - buffer->available = chunk; - -#ifndef NDEBUG - --buffer->num_allocated; -#endif - - g_mutex_unlock(buffer->mutex); -} diff --git a/src/buffer.h b/src/buffer.h deleted file mode 100644 index f860231e7..000000000 --- a/src/buffer.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MUSIC_BUFFER_H -#define MPD_MUSIC_BUFFER_H - -/** - * An allocator for #music_chunk objects. - */ -struct music_buffer; - -/** - * Creates a new #music_buffer object. - * - * @param num_chunks the number of #music_chunk reserved in this - * buffer - */ -struct music_buffer * -music_buffer_new(unsigned num_chunks); - -/** - * Frees the #music_buffer object - */ -void -music_buffer_free(struct music_buffer *buffer); - -/** - * Returns the total number of reserved chunks in this buffer. This - * is the same value which was passed to the constructor - * music_buffer_new(). - */ -unsigned -music_buffer_size(const struct music_buffer *buffer); - -/** - * Allocates a chunk from the buffer. When it is not used anymore, - * call music_buffer_return(). - * - * @return an empty chunk or NULL if there are no chunks available - */ -struct music_chunk * -music_buffer_allocate(struct music_buffer *buffer); - -/** - * Returns a chunk to the buffer. It can be reused by - * music_buffer_allocate() then. - */ -void -music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk); - -#endif diff --git a/src/chunk.c b/src/chunk.c deleted file mode 100644 index 1eb96f4b9..000000000 --- a/src/chunk.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "chunk.h" -#include "audio_format.h" -#include "tag.h" - -#include <assert.h> - -void -music_chunk_init(struct music_chunk *chunk) -{ - chunk->other = NULL; - chunk->length = 0; - chunk->tag = NULL; - chunk->replay_gain_serial = 0; -} - -void -music_chunk_free(struct music_chunk *chunk) -{ - if (chunk->tag != NULL) - tag_free(chunk->tag); -} - -#ifndef NDEBUG -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format) -{ - assert(chunk != NULL); - assert(audio_format != NULL); - assert(audio_format_valid(audio_format)); - - return chunk->length == 0 || - audio_format_equals(&chunk->audio_format, audio_format); -} -#endif - -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - size_t num_frames; - - assert(music_chunk_check_format(chunk, audio_format)); - assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - - if (chunk->length == 0) { - /* if the chunk is empty, nobody has set bitRate and - times yet */ - - chunk->bit_rate = bit_rate; - chunk->times = data_time; - } - - num_frames = (sizeof(chunk->data) - chunk->length) / frame_size; - if (num_frames == 0) - return NULL; - -#ifndef NDEBUG - chunk->audio_format = *audio_format; -#endif - - *max_length_r = num_frames * frame_size; - return chunk->data + chunk->length; -} - -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length) -{ - const size_t frame_size = audio_format_frame_size(audio_format); - - assert(chunk != NULL); - assert(chunk->length + length <= sizeof(chunk->data)); - assert(audio_format_equals(&chunk->audio_format, audio_format)); - - chunk->length += length; - - return chunk->length + frame_size > sizeof(chunk->data); -} diff --git a/src/chunk.h b/src/chunk.h deleted file mode 100644 index a06a203eb..000000000 --- a/src/chunk.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CHUNK_H -#define MPD_CHUNK_H - -#include "replay_gain_info.h" - -#ifndef NDEBUG -#include "audio_format.h" -#endif - -#include <stdbool.h> -#include <stdint.h> -#include <stddef.h> - -enum { - CHUNK_SIZE = 4096, -}; - -struct audio_format; - -/** - * A chunk of music data. Its format is defined by the - * music_pipe_append() caller. - */ -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; - - /** current bit rate of the source file */ - uint16_t bit_rate; - - /** the time stamp within the song */ - float times; - - /** - * An optional tag associated with this chunk (and the - * following chunks); appears at song boundaries. The tag - * object is owned by this chunk, and must be freed when this - * chunk is deinitialized in music_chunk_free() - */ - 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]; - -#ifndef NDEBUG - struct audio_format audio_format; -#endif -}; - -void -music_chunk_init(struct music_chunk *chunk); - -void -music_chunk_free(struct music_chunk *chunk); - -static inline bool -music_chunk_is_empty(const struct music_chunk *chunk) -{ - return chunk->length == 0 && chunk->tag == NULL; -} - -#ifndef NDEBUG -/** - * Checks if the audio format if the chunk is equal to the specified - * audio_format. - */ -bool -music_chunk_check_format(const struct music_chunk *chunk, - const struct audio_format *audio_format); -#endif - -/** - * Prepares appending to the music chunk. Returns a buffer where you - * may write into. After you are finished, call music_chunk_expand(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param data_time the time within the song - * @param bit_rate the current bit rate of the source file - * @param max_length_r the maximum write length is returned here - * @return a writable buffer, or NULL if the chunk is full - */ -void * -music_chunk_write(struct music_chunk *chunk, - const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r); - -/** - * Increases the length of the chunk after the caller has written to - * the buffer returned by music_chunk_write(). - * - * @param chunk the music_chunk object - * @param audio_format the audio format for the appended data; must - * stay the same for the life cycle of this chunk - * @param length the number of bytes which were appended - * @return true if the chunk is full - */ -bool -music_chunk_expand(struct music_chunk *chunk, - const struct audio_format *audio_format, size_t length); - -#endif diff --git a/src/client.c b/src/client.c deleted file mode 100644 index 3fa2c9be4..000000000 --- a/src/client.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" - -bool client_is_expired(const struct client *client) -{ - return client->channel == NULL; -} - -int client_get_uid(const struct client *client) -{ - return client->uid; -} - -unsigned client_get_permission(const struct client *client) -{ - return client->permission; -} - -void client_set_permission(struct client *client, unsigned permission) -{ - client->permission = permission; -} diff --git a/src/client.h b/src/client.h deleted file mode 100644 index 0302a2e0a..000000000 --- a/src/client.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_H -#define MPD_CLIENT_H - -#include <glib.h> -#include <stdbool.h> -#include <stddef.h> -#include <stdarg.h> - -struct client; -struct sockaddr; -struct player_control; - -void client_manager_init(void); -void client_manager_deinit(void); - -void client_new(struct player_control *player_control, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); - -G_GNUC_PURE -bool client_is_expired(const struct client *client); - -/** - * returns the uid of the client process, or a negative value if the - * uid is unknown - */ -G_GNUC_PURE -int client_get_uid(const struct client *client); - -/** - * Is this client running on the same machine, connected with a local - * (UNIX domain) socket? - */ -G_GNUC_PURE -static inline bool -client_is_local(const struct client *client) -{ - return client_get_uid(client) > 0; -} - -G_GNUC_PURE -unsigned client_get_permission(const struct client *client); - -void client_set_permission(struct client *client, unsigned permission); - -/** - * Write a C string to the client. - */ -void client_puts(struct client *client, const char *s); - -/** - * Write a printf-like formatted string to the client. - */ -void client_vprintf(struct client *client, const char *fmt, va_list args); - -/** - * Write a printf-like formatted string to the client. - */ -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); - -#endif diff --git a/src/client_event.c b/src/client_event.c deleted file mode 100644 index 4f54ae0a7..000000000 --- a/src/client_event.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 1ca32ebcc..000000000 --- a/src/client_expire.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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_file.c b/src/client_file.c deleted file mode 100644 index 2ee433308..000000000 --- a/src/client_file.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_file.h" -#include "client.h" -#include "ack.h" - -#include <sys/stat.h> -#include <sys/types.h> -#include <errno.h> -#include <unistd.h> - -bool -client_allow_file(const struct client *client, const char *path_fs, - GError **error_r) -{ -#ifdef WIN32 - (void)client; - (void)path_fs; - - g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, - "Access denied"); - return false; -#else - const int uid = client_get_uid(client); - if (uid >= 0 && (uid_t)uid == geteuid()) - /* always allow access if user runs his own MPD - instance */ - return true; - - if (uid <= 0) { - /* unauthenticated client */ - g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, - "Access denied"); - return false; - } - - struct stat st; - if (stat(path_fs, &st) < 0) { - g_set_error(error_r, g_file_error_quark(), errno, - "%s", g_strerror(errno)); - return false; - } - - if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { - /* client is not owner */ - g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, - "Access denied"); - return false; - } - - return true; -#endif -} diff --git a/src/client_file.h b/src/client_file.h deleted file mode 100644 index bc64bd041..000000000 --- a/src/client_file.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_FILE_H -#define MPD_CLIENT_FILE_H - -#include <glib.h> -#include <stdbool.h> - -struct client; - -/** - * Is this client allowed to use the specified local file? - * - * Note that this function is vulnerable to timing/symlink attacks. - * We cannot fix this as long as there are plugins that open a file by - * its name, and not by file descriptor / callbacks. - * - * @param path_fs the absolute path name in filesystem encoding - * @return true if access is allowed - */ -bool -client_allow_file(const struct client *client, const char *path_fs, - GError **error_r); - -#endif diff --git a/src/client_global.c b/src/client_global.c deleted file mode 100644 index adf3b2f9e..000000000 --- a/src/client_global.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 930911d6e..000000000 --- a/src/client_idle.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_idle.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); -} - -void -client_idle_add(struct client *client, unsigned flags) -{ - 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); - } -} - -static void -client_idle_callback(gpointer data, gpointer user_data) -{ - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - - client_idle_add(client, flags); -} - -void client_manager_idle_add(unsigned flags) -{ - assert(flags != 0); - - 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_idle.h b/src/client_idle.h deleted file mode 100644 index c56fd014c..000000000 --- a/src/client_idle.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_IDLE_H -#define MPD_CLIENT_IDLE_H - -#include <stdbool.h> - -struct client; - -void -client_idle_add(struct client *client, unsigned flags); - -/** - * Adds the specified idle flags to all clients and immediately sends - * notifications to all waiting clients. - */ -void -client_manager_idle_add(unsigned flags); - -/** - * Checks whether the client has pending idle flags. If yes, they are - * sent immediately and "true" is returned". If no, it puts the - * client into waiting mode and returns false. - */ -bool -client_idle_wait(struct client *client, unsigned flags); - -#endif diff --git a/src/client_internal.h b/src/client_internal.h deleted file mode 100644 index ba97e4b8f..000000000 --- a/src/client_internal.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_INTERNAL_H -#define MPD_CLIENT_INTERNAL_H - -#include "client.h" -#include "client_message.h" -#include "command.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "client" - -enum { - CLIENT_MAX_SUBSCRIPTIONS = 16, - CLIENT_MAX_MESSAGES = 64, -}; - -struct deferred_buffer { - size_t size; - char data[sizeof(long)]; -}; - -struct client { - struct player_control *player_control; - - GIOChannel *channel; - guint source_id; - - /** 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; - - /** - * A list of channel names this client is subscribed to. - */ - GSList *subscriptions; - - /** - * The number of subscriptions in #subscriptions. Used to - * limit the number of subscriptions. - */ - unsigned num_subscriptions; - - /** - * A list of messages this client has received in reverse - * order (latest first). - */ - GSList *messages; - - /** - * The number of messages in #messages. - */ - unsigned num_messages; -}; - -extern unsigned int client_max_connections; -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 deleted file mode 100644 index 2c7f37aff..000000000 --- a/src/client_list.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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_message.c b/src/client_message.c deleted file mode 100644 index b681b4e7f..000000000 --- a/src/client_message.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_message.h" - -#include <assert.h> -#include <glib.h> - -G_GNUC_PURE -static bool -valid_channel_char(const char ch) -{ - return g_ascii_isalnum(ch) || - ch == '_' || ch == '-' || ch == '.' || ch == ':'; -} - -bool -client_message_valid_channel_name(const char *name) -{ - do { - if (!valid_channel_char(*name)) - return false; - } while (*++name != 0); - - return true; -} - -void -client_message_init_null(struct client_message *msg) -{ - assert(msg != NULL); - - msg->channel = NULL; - msg->message = NULL; -} - -void -client_message_init(struct client_message *msg, - const char *channel, const char *message) -{ - assert(msg != NULL); - - msg->channel = g_strdup(channel); - msg->message = g_strdup(message); -} - -void -client_message_copy(struct client_message *dest, - const struct client_message *src) -{ - assert(dest != NULL); - assert(src != NULL); - assert(client_message_defined(src)); - - client_message_init(dest, src->channel, src->message); -} - -struct client_message * -client_message_dup(const struct client_message *src) -{ - struct client_message *dest = g_slice_new(struct client_message); - client_message_copy(dest, src); - return dest; -} - -void -client_message_deinit(struct client_message *msg) -{ - assert(msg != NULL); - - g_free(msg->channel); - g_free(msg->message); -} - -void -client_message_free(struct client_message *msg) -{ - client_message_deinit(msg); - g_slice_free(struct client_message, msg); -} diff --git a/src/client_message.h b/src/client_message.h deleted file mode 100644 index 38c2e7615..000000000 --- a/src/client_message.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_MESSAGE_H -#define MPD_CLIENT_MESSAGE_H - -#include <assert.h> -#include <stdbool.h> -#include <stddef.h> -#include <glib.h> - -/** - * A client-to-client message. - */ -struct client_message { - char *channel; - - char *message; -}; - -G_GNUC_PURE -bool -client_message_valid_channel_name(const char *name); - -G_GNUC_PURE -static inline bool -client_message_defined(const struct client_message *msg) -{ - assert(msg != NULL); - assert((msg->channel == NULL) == (msg->message == NULL)); - - return msg->channel != NULL; -} - -void -client_message_init_null(struct client_message *msg); - -void -client_message_init(struct client_message *msg, - const char *channel, const char *message); - -void -client_message_copy(struct client_message *dest, - const struct client_message *src); - -G_GNUC_MALLOC -struct client_message * -client_message_dup(const struct client_message *src); - -void -client_message_deinit(struct client_message *msg); - -void -client_message_free(struct client_message *msg); - -#endif diff --git a/src/client_new.c b/src/client_new.c deleted file mode 100644 index cf28c43c5..000000000 --- a/src/client_new.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "client_internal.h" -#include "fd_util.h" -#include "fifo_buffer.h" -#include "resolver.h" -#include "permission.h" -#include "glib_socket.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(struct player_control *player_control, - int fd, const struct sockaddr *sa, size_t sa_length, int uid) -{ - static unsigned int next_client_num; - struct client *client; - char *remote; - - assert(player_control != NULL); - assert(fd >= 0); - -#ifdef HAVE_LIBWRAP - 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_socket(fd); - return; - } - - g_free(hostaddr); - } -#endif /* HAVE_WRAP */ - - if (client_list_is_full()) { - g_warning("Max Connections Reached!"); - close_socket(fd); - return; - } - - client = g_new0(struct client, 1); - client->player_control = player_control; - - client->channel = g_io_channel_new_socket(fd); - /* GLib is responsible for closing the file descriptor */ - 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; - - client->subscriptions = NULL; - client->messages = NULL; - client->num_messages = 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 deleted file mode 100644 index 57a8a7824..000000000 --- a/src/client_process.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 26ade264e..000000000 --- a/src/client_read.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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_subscribe.c b/src/client_subscribe.c deleted file mode 100644 index c65a7ed31..000000000 --- a/src/client_subscribe.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "client_subscribe.h" -#include "client_internal.h" -#include "client_idle.h" -#include "idle.h" - -#include <string.h> - -G_GNUC_PURE -static GSList * -client_find_subscription(const struct client *client, const char *channel) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - if (strcmp((const char *)i->data, channel) == 0) - return i; - - return NULL; -} - -enum client_subscribe_result -client_subscribe(struct client *client, const char *channel) -{ - assert(client != NULL); - assert(channel != NULL); - - if (!client_message_valid_channel_name(channel)) - return CLIENT_SUBSCRIBE_INVALID; - - if (client_find_subscription(client, channel) != NULL) - return CLIENT_SUBSCRIBE_ALREADY; - - if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) - return CLIENT_SUBSCRIBE_FULL; - - client->subscriptions = g_slist_prepend(client->subscriptions, - g_strdup(channel)); - ++client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - return CLIENT_SUBSCRIBE_OK; -} - -bool -client_unsubscribe(struct client *client, const char *channel) -{ - GSList *i = client_find_subscription(client, channel); - if (i == NULL) - return false; - - assert(client->num_subscriptions > 0); - - client->subscriptions = g_slist_remove(client->subscriptions, i->data); - --client->num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - assert((client->num_subscriptions == 0) == - (client->subscriptions == NULL)); - - return true; -} - -void -client_unsubscribe_all(struct client *client) -{ - for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) - g_free(i->data); - - g_slist_free(client->subscriptions); - client->subscriptions = NULL; - client->num_subscriptions = 0; -} - -bool -client_push_message(struct client *client, const struct client_message *msg) -{ - assert(client != NULL); - assert(msg != NULL); - assert(client_message_defined(msg)); - - if (client->num_messages >= CLIENT_MAX_MESSAGES || - client_find_subscription(client, msg->channel) == NULL) - return false; - - if (client->messages == NULL) - client_idle_add(client, IDLE_MESSAGE); - - client->messages = g_slist_prepend(client->messages, - client_message_dup(msg)); - ++client->num_messages; - - return true; -} - -GSList * -client_read_messages(struct client *client) -{ - GSList *messages = g_slist_reverse(client->messages); - - client->messages = NULL; - client->num_messages = 0; - - return messages; -} diff --git a/src/client_subscribe.h b/src/client_subscribe.h deleted file mode 100644 index 09f864417..000000000 --- a/src/client_subscribe.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_SUBSCRIBE_H -#define MPD_CLIENT_SUBSCRIBE_H - -#include <stdbool.h> -#include <glib.h> - -struct client; -struct client_message; - -enum client_subscribe_result { - /** success */ - CLIENT_SUBSCRIBE_OK, - - /** invalid channel name */ - CLIENT_SUBSCRIBE_INVALID, - - /** already subscribed to this channel */ - CLIENT_SUBSCRIBE_ALREADY, - - /** too many subscriptions */ - CLIENT_SUBSCRIBE_FULL, -}; - -enum client_subscribe_result -client_subscribe(struct client *client, const char *channel); - -bool -client_unsubscribe(struct client *client, const char *channel); - -void -client_unsubscribe_all(struct client *client); - -bool -client_push_message(struct client *client, const struct client_message *msg); - -G_GNUC_MALLOC -GSList * -client_read_messages(struct client *client); - -#endif diff --git a/src/client_write.c b/src/client_write.c deleted file mode 100644 index 78cfca8a1..000000000 --- a/src/client_write.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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/clock.h b/src/clock.h deleted file mode 100644 index 4ece35ab1..000000000 --- a/src/clock.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLOCK_H -#define MPD_CLOCK_H - -#include <glib.h> - -#include <stdint.h> - -/** - * Returns the value of a monotonic clock in milliseconds. - */ -G_GNUC_PURE -unsigned -monotonic_clock_ms(void); - -/** - * Returns the value of a monotonic clock in microseconds. - */ -G_GNUC_PURE -uint64_t -monotonic_clock_us(void); - -#endif diff --git a/src/cmdline.c b/src/cmdline.c deleted file mode 100644 index cb7eff36a..000000000 --- a/src/cmdline.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "cmdline.h" -#include "path.h" -#include "log.h" -#include "conf.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "output_list.h" -#include "output_plugin.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "ls.h" -#include "mpd_error.h" -#include "glib_compat.h" - -#ifdef ENABLE_ENCODER -#include "encoder_list.h" -#include "encoder_plugin.h" -#endif - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#include "archive_plugin.h" -#endif - -#include <glib.h> - -#include <stdio.h> -#include <stdlib.h> - -#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"); -} - -G_GNUC_NORETURN -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-2012 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" - "Decoders plugins:"); - - decoder_plugins_for_each(plugin) { - printf(" [%s]", plugin->name); - - const char *const*suffixes = plugin->suffixes; - if (suffixes != NULL) - for (; *suffixes != NULL; ++suffixes) - printf(" %s", *suffixes); - - puts(""); - } - - puts("\n" - "Output plugins:"); - audio_output_plugins_for_each(plugin) - printf(" %s", plugin->name); - puts(""); - -#ifdef ENABLE_ENCODER - puts("\n" - "Encoder plugins:"); - encoder_plugins_for_each(plugin) - printf(" %s", plugin->name); - puts(""); -#endif - -#ifdef ENABLE_ARCHIVE - puts("\n" - "Archive plugins:"); - archive_plugins_for_each(plugin) { - printf(" [%s]", plugin->name); - - const char *const*suffixes = plugin->suffixes; - if (suffixes != NULL) - for (; *suffixes != NULL; ++suffixes) - printf(" %s", *suffixes); - - puts(""); - } -#endif - - puts("\n" - "Input plugins:"); - input_plugins_for_each(plugin) - printf(" %s", plugin->name); - - puts("\n\n" - "Playlist plugins:"); - playlist_plugins_for_each(plugin) - printf(" %s", plugin->name); - - puts("\n\n" - "Protocols:"); - print_supported_uri_schemes_to_fp(stdout); - - exit(EXIT_SUCCESS); -} - -static const char *summary = - "Music Player Daemon - a daemon for playing music."; - -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_no_daemon, - option_no_config; - const GOptionEntry entries[] = { - { "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-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, - "don't detach from console", NULL }, - { "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 }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, - "print version number", NULL }, - { .long_name = NULL } - }; - - options->kill = false; - options->daemon = true; - options->log_stderr = false; - options->verbose = false; - - context = g_option_context_new("[path/to/mpd.conf]"); - g_option_context_add_main_entries(context, entries, NULL); - - g_option_context_set_summary(context, summary); - - ret = g_option_context_parse(context, &argc, &argv, &error); - g_option_context_free(context); - - if (!ret) - MPD_ERROR("option parsing failed: %s\n", error->message); - - if (option_version) - version(); - - /* initialize the logging library, so the configuration file - parser can use it already */ - log_early_init(options->verbose); - - 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; - -#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); - break; - } else - g_free(system_path); - ++i; - } - } -#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)) - ret = config_read_file(path1, error_r); - else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR)) - ret = config_read_file(path2, error_r); - else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION, - G_FILE_TEST_IS_REGULAR)) - 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 */ - 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 deleted file mode 100644 index 68f625a6c..000000000 --- a/src/cmdline.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef CMDLINE_H -#define CMDLINE_H - -#include <glib.h> - -#include <stdbool.h> - -struct options { - gboolean kill; - gboolean daemon; - gboolean log_stderr; - gboolean verbose; -}; - -bool -parse_cmdline(int argc, char **argv, struct options *options, - GError **error_r); - -#endif diff --git a/src/command.c b/src/command.c deleted file mode 100644 index b3318c68b..000000000 --- a/src/command.c +++ /dev/null @@ -1,2302 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "command.h" -#include "protocol/argparser.h" -#include "protocol/result.h" -#include "player_control.h" -#include "playlist.h" -#include "playlist_print.h" -#include "playlist_save.h" -#include "playlist_queue.h" -#include "playlist_error.h" -#include "queue_print.h" -#include "ls.h" -#include "uri.h" -#include "decoder_print.h" -#include "directory.h" -#include "database.h" -#include "update.h" -#include "volume.h" -#include "stats.h" -#include "permission.h" -#include "tokenizer.h" -#include "stored_playlist.h" -#include "ack.h" -#include "output_command.h" -#include "output_print.h" -#include "locate.h" -#include "dbUtils.h" -#include "db_error.h" -#include "db_print.h" -#include "db_selection.h" -#include "db_lock.h" -#include "tag.h" -#include "client.h" -#include "client_idle.h" -#include "client_internal.h" -#include "client_subscribe.h" -#include "client_file.h" -#include "tag_print.h" -#include "path.h" -#include "replay_gain_config.h" -#include "idle.h" -#include "mapper.h" -#include "song.h" -#include "song_print.h" - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#include "sticker_print.h" -#include "song_sticker.h" -#endif - -#include <assert.h> -#include <time.h> -#include <stdlib.h> -#include <errno.h> - -#define COMMAND_STATUS_STATE "state" -#define COMMAND_STATUS_REPEAT "repeat" -#define COMMAND_STATUS_SINGLE "single" -#define COMMAND_STATUS_CONSUME "consume" -#define COMMAND_STATUS_RANDOM "random" -#define COMMAND_STATUS_PLAYLIST "playlist" -#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" -#define COMMAND_STATUS_SONG "song" -#define COMMAND_STATUS_SONGID "songid" -#define COMMAND_STATUS_NEXTSONG "nextsong" -#define COMMAND_STATUS_NEXTSONGID "nextsongid" -#define COMMAND_STATUS_TIME "time" -#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" - -/* - * The most we ever use is for search/find, and that limits it to the - * number of tags we can have. Add one for the command, and one extra - * to catch errors clients may send us - */ -#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) - -/* if min: -1 don't check args * - * if max: -1 no max args */ -struct command { - const char *cmd; - unsigned permission; - int min; - int max; - enum command_return (*handler)(struct client *client, int argc, char **argv); -}; - -static enum command_return -print_playlist_result(struct client *client, - enum playlist_result result) -{ - switch (result) { - case PLAYLIST_RESULT_SUCCESS: - return COMMAND_RETURN_OK; - - case PLAYLIST_RESULT_ERRNO: - command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_DENIED: - command_error(client, ACK_ERROR_PERMISSION, "Access denied"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NO_SUCH_SONG: - command_error(client, ACK_ERROR_NO_EXIST, "No such song"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NO_SUCH_LIST: - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_LIST_EXISTS: - command_error(client, ACK_ERROR_EXIST, - "Playlist already exists"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_BAD_NAME: - command_error(client, ACK_ERROR_ARG, - "playlist name is invalid: " - "playlist names may not contain slashes," - " newlines or carriage returns"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_BAD_RANGE: - command_error(client, ACK_ERROR_ARG, "Bad song index"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_NOT_PLAYING: - command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_TOO_LARGE: - command_error(client, ACK_ERROR_PLAYLIST_MAX, - "playlist is at the max size"); - return COMMAND_RETURN_ERROR; - - case PLAYLIST_RESULT_DISABLED: - command_error(client, ACK_ERROR_UNKNOWN, - "stored playlist support is disabled"); - return COMMAND_RETURN_ERROR; - } - - assert(0); - return COMMAND_RETURN_ERROR; -} - -/** - * Send the GError to the client and free the GError. - */ -static enum command_return -print_error(struct client *client, GError *error) -{ - assert(client != NULL); - assert(error != NULL); - - g_warning("%s", error->message); - - if (error->domain == playlist_quark()) { - enum playlist_result result = error->code; - g_error_free(error); - return print_playlist_result(client, result); - } else if (error->domain == ack_quark()) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } else if (error->domain == db_quark()) { - switch ((enum db_error)error->code) { - case DB_DISABLED: - command_error(client, ACK_ERROR_NO_EXIST, "%s", - error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - - case DB_NOT_FOUND: - g_error_free(error); - command_error(client, ACK_ERROR_NO_EXIST, "Not found"); - return COMMAND_RETURN_ERROR; - } - } else if (error->domain == g_file_error_quark()) { - command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(error->code)); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - - g_error_free(error); - command_error(client, ACK_ERROR_UNKNOWN, "error"); - return COMMAND_RETURN_ERROR; -} - -static void -print_spl_list(struct client *client, GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - time_t t; -#ifndef WIN32 - struct tm tm; -#endif - char timestamp[32]; - - client_printf(client, "playlist: %s\n", playlist->name); - - t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&t) -#else - "%FT%TZ", - gmtime_r(&t, &tm) -#endif - ); - client_printf(client, "Last-Modified: %s\n", timestamp); - } -} - -static enum command_return -handle_urlhandlers(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - if (client_is_local(client)) - client_puts(client, "handler: file://\n"); - print_supported_uri_schemes(client); - return COMMAND_RETURN_OK; -} - -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[]) -{ - tag_print_types(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_play(struct client *client, int argc, char *argv[]) -{ - int song = -1; - enum playlist_result result; - - if (argc == 2 && !check_int(client, &song, argv[1])) - return COMMAND_RETURN_ERROR; - result = playlist_play(&g_playlist, client->player_control, song); - return print_playlist_result(client, result); -} - -static enum command_return -handle_playid(struct client *client, int argc, char *argv[]) -{ - int id = -1; - enum playlist_result result; - - if (argc == 2 && !check_int(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_play_id(&g_playlist, client->player_control, id); - return print_playlist_result(client, result); -} - -static enum command_return -handle_stop(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_stop(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_currentsong(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_print_current(client, &g_playlist); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_pause(struct client *client, - int argc, char *argv[]) -{ - if (argc == 2) { - bool pause_flag; - if (!check_bool(client, &pause_flag, argv[1])) - return COMMAND_RETURN_ERROR; - - pc_set_pause(client->player_control, pause_flag); - } else - pc_pause(client->player_control); - - return COMMAND_RETURN_OK; -} - -static enum command_return -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; - - pc_get_status(client->player_control, &player_status); - - switch (player_status.state) { - case PLAYER_STATE_STOP: - state = "stop"; - break; - case PLAYER_STATE_PAUSE: - state = "pause"; - break; - case PLAYER_STATE_PLAY: - state = "play"; - break; - } - - client_printf(client, - "volume: %i\n" - COMMAND_STATUS_REPEAT ": %i\n" - COMMAND_STATUS_RANDOM ": %i\n" - COMMAND_STATUS_SINGLE ": %i\n" - COMMAND_STATUS_CONSUME ": %i\n" - 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(), - 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(client->player_control) + 0.5), - pc_get_mixramp_db(client->player_control), - pc_get_mixramp_delay(client->player_control), - state); - - song = playlist_get_current_song(&g_playlist); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_SONG ": %i\n" - COMMAND_STATUS_SONGID ": %u\n", - song, playlist_get_song_id(&g_playlist, song)); - } - - if (player_status.state != PLAYER_STATE_STOP) { - client_printf(client, - COMMAND_STATUS_TIME ": %i:%i\n" - "elapsed: %1.3f\n" - COMMAND_STATUS_BITRATE ": %u\n", - (int)(player_status.elapsed_time + 0.5), - (int)(player_status.total_time + 0.5), - player_status.elapsed_time, - player_status.bit_rate); - - if (audio_format_defined(&player_status.audio_format)) { - struct audio_format_string af_string; - - client_printf(client, - COMMAND_STATUS_AUDIO ": %s\n", - audio_format_to_string(&player_status.audio_format, - &af_string)); - } - } - - if ((updateJobId = isUpdatingDB())) { - client_printf(client, - COMMAND_STATUS_UPDATING_DB ": %i\n", - updateJobId); - } - - error = pc_get_error_message(client->player_control); - if (error != NULL) { - client_printf(client, - COMMAND_STATUS_ERROR ": %s\n", - error); - g_free(error); - } - - song = playlist_get_next_song(&g_playlist); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_NEXTSONG ": %i\n" - COMMAND_STATUS_NEXTSONGID ": %u\n", - song, playlist_get_song_id(&g_playlist, song)); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_kill(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_KILL; -} - -static enum command_return -handle_close(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_CLOSE; -} - -static enum command_return -handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *uri = argv[1]; - enum playlist_result result; - - if (strncmp(uri, "file:///", 8) == 0) { - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - result = playlist_append_file(&g_playlist, - client->player_control, - path, - NULL); - return print_playlist_result(client, result); - } - - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - result = playlist_append_uri(&g_playlist, - client->player_control, - uri, NULL); - return print_playlist_result(client, result); - } - - GError *error = NULL; - return addAllIn(client->player_control, uri, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_addid(struct client *client, int argc, char *argv[]) -{ - char *uri = argv[1]; - unsigned added_id; - enum playlist_result result; - - if (strncmp(uri, "file:///", 8) == 0) { - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - result = playlist_append_file(&g_playlist, - client->player_control, - path, - &added_id); - } else { - if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - result = playlist_append_uri(&g_playlist, - client->player_control, - uri, &added_id); - } - - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - - if (argc == 3) { - unsigned to; - if (!check_unsigned(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, client->player_control, - added_id, to); - if (result != PLAYLIST_RESULT_SUCCESS) { - enum command_return ret = - print_playlist_result(client, result); - playlist_delete_id(&g_playlist, client->player_control, - added_id); - return ret; - } - } - - client_printf(client, "Id: %u\n", added_id); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned start, end; - enum playlist_result result; - - if (!check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_delete_range(&g_playlist, client->player_control, - start, end); - return print_playlist_result(client, result); -} - -static enum command_return -handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - result = playlist_delete_id(&g_playlist, client->player_control, id); - return print_playlist_result(client, result); -} - -static enum command_return -handle_playlist(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_print_uris(client, &g_playlist); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_shuffle(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - unsigned start = 0, end = queue_length(&g_playlist.queue); - if (argc == 2 && !check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_shuffle(&g_playlist, client->player_control, start, end); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_clear(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_clear(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_save(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - enum playlist_result result; - - result = spl_save_playlist(argv[1], &g_playlist); - return print_playlist_result(client, result); -} - -static enum command_return -handle_load(struct client *client, int argc, char *argv[]) -{ - unsigned start_index, end_index; - - if (argc < 3) { - start_index = 0; - end_index = G_MAXUINT; - } else if (!check_range(client, &start_index, &end_index, argv[2])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result; - - result = playlist_open_into_queue(argv[1], - start_index, end_index, - &g_playlist, - client->player_control, true); - if (result != PLAYLIST_RESULT_NO_SUCH_LIST) - return print_playlist_result(client, result); - - GError *error = NULL; - if (playlist_load_spl(&g_playlist, client->player_control, - argv[1], start_index, end_index, - &error)) - return COMMAND_RETURN_OK; - - if (error->domain == playlist_quark() && - error->code == PLAYLIST_RESULT_BAD_NAME) - /* the message for BAD_NAME is confusing when the - client wants to load a playlist file from the music - directory; patch the GError object to show "no such - playlist" instead */ - error->code = PLAYLIST_RESULT_NO_SUCH_LIST; - - return print_error(client, error); -} - -static enum command_return -handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - if (playlist_file_print(client, argv[1], false)) - return COMMAND_RETURN_OK; - - GError *error = NULL; - return spl_print(client, argv[1], false, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_listplaylistinfo(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) -{ - if (playlist_file_print(client, argv[1], true)) - return COMMAND_RETURN_OK; - - GError *error = NULL; - return spl_print(client, argv[1], true, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_lsinfo(struct client *client, int argc, char *argv[]) -{ - const char *uri; - - if (argc == 2) - uri = argv[1]; - else - /* default is root directory */ - uri = ""; - - if (strncmp(uri, "file:///", 8) == 0) { - /* print information about an arbitrary local file */ - const char *path = uri + 7; - - GError *error = NULL; - if (!client_allow_file(client, path, &error)) - return print_error(client, error); - - struct song *song = song_file_load(path, NULL); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such file"); - return COMMAND_RETURN_ERROR; - } - - song_print_info(client, song); - song_free(song); - return COMMAND_RETURN_OK; - } - - struct db_selection selection; - db_selection_init(&selection, uri, false); - - GError *error = NULL; - if (!db_selection_print(client, &selection, true, &error)) - return print_error(client, error); - - if (isRootDirectory(uri)) { - GPtrArray *list = spl_list(NULL); - if (list != NULL) { - print_spl_list(client, list); - spl_list_free(list); - } - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_delete(argv[1], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_rename(argv[1], argv[2], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - uint32_t version; - - if (!check_uint32(client, &version, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_print_changes_info(client, &g_playlist, version); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - uint32_t version; - - if (!check_uint32(client, &version, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_print_changes_position(client, &g_playlist, version); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistinfo(struct client *client, int argc, char *argv[]) -{ - unsigned start = 0, end = G_MAXUINT; - bool ret; - - if (argc == 2 && !check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = playlist_print_info(client, &g_playlist, start, end); - if (!ret) - return print_playlist_result(client, - PLAYLIST_RESULT_BAD_RANGE); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistid(struct client *client, int argc, char *argv[]) -{ - if (argc >= 2) { - unsigned id; - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - - bool ret = playlist_print_id(client, &g_playlist, id); - if (!ret) - return print_playlist_result(client, - PLAYLIST_RESULT_NO_SUCH_SONG); - } else { - playlist_print_info(client, &g_playlist, 0, G_MAXUINT); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_find(struct client *client, int argc, char *argv[]) -{ - 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; - } - - GError *error = NULL; - enum command_return ret = findSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_findadd(struct client *client, int argc, char *argv[]) -{ - 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; - } - - GError *error = NULL; - enum command_return ret = - findAddIn(client->player_control, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_search(struct client *client, int argc, char *argv[]) -{ - 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; - } - - GError *error = NULL; - enum command_return ret = searchForSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_searchadd(struct client *client, int argc, char *argv[]) -{ - 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; - } - - GError *error = NULL; - enum command_return ret = search_add_songs(client->player_control, - "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_searchaddpl(struct client *client, int argc, char *argv[]) -{ - const char *playlist = argv[1]; - - struct locate_item_list *list = - locate_item_list_parse(argv + 2, argc - 2); - - 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; - } - - GError *error = NULL; - enum command_return ret = - search_add_to_playlist("", playlist, list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_count(struct client *client, int argc, char *argv[]) -{ - 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; - } - - GError *error = NULL; - enum command_return ret = - searchStatsForSongsIn(client, "", list, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(list); - - return ret; -} - -static enum command_return -handle_playlistfind(struct client *client, int argc, char *argv[]) -{ - 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; - } - - playlist_print_find(client, &g_playlist, list); - - locate_item_list_free(list); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistsearch(struct client *client, int argc, char *argv[]) -{ - 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; - } - - playlist_print_search(client, &g_playlist, list); - - locate_item_list_free(list); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistdelete(struct client *client, - G_GNUC_UNUSED int argc, char *argv[]) { - char *playlist = argv[1]; - unsigned from; - - if (!check_unsigned(client, &from, argv[2])) - return COMMAND_RETURN_ERROR; - - GError *error = NULL; - return spl_remove_index(playlist, from, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *playlist = argv[1]; - unsigned from, to; - - if (!check_unsigned(client, &from, argv[2])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &to, argv[3])) - return COMMAND_RETURN_ERROR; - - GError *error = NULL; - return spl_move_index(playlist, from, to, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_update(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 (*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 = 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; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_next(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - /* single mode is not considered when this is user who - * wants to change song. */ - const bool single = g_playlist.queue.single; - g_playlist.queue.single = false; - - playlist_next(&g_playlist, client->player_control); - - g_playlist.queue.single = single; - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_previous(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - playlist_previous(&g_playlist, client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_prio(struct client *client, int argc, char *argv[]) -{ - unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) - return COMMAND_RETURN_ERROR; - - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return COMMAND_RETURN_ERROR; - } - - for (int i = 2; i < argc; ++i) { - unsigned start_position, end_position; - if (!check_range(client, &start_position, &end_position, - argv[i])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_set_priority(&g_playlist, - client->player_control, - start_position, end_position, - priority); - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_prioid(struct client *client, int argc, char *argv[]) -{ - unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) - return COMMAND_RETURN_ERROR; - - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return COMMAND_RETURN_ERROR; - } - - for (int i = 2; i < argc; ++i) { - unsigned song_id; - if (!check_unsigned(client, &song_id, argv[i])) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_set_priority_id(&g_playlist, - client->player_control, - song_id, priority); - if (result != PLAYLIST_RESULT_SUCCESS) - return print_playlist_result(client, result); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; - - GError *error = NULL; - return printAllIn(client, directory, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned level; - bool success; - - if (!check_unsigned(client, &level, argv[1])) - return COMMAND_RETURN_ERROR; - - if (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"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_repeat(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_single(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_consume(&g_playlist, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - bool status; - if (!check_bool(client, &status, argv[1])) - return COMMAND_RETURN_ERROR; - - playlist_set_random(&g_playlist, client->player_control, status); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_stats(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return stats_print(client); -} - -static enum command_return -handle_clearerror(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - pc_clear_error(client->player_control); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_list(struct client *client, int argc, char *argv[]) -{ - struct locate_item_list *conditionals; - int tagType = locate_parse_type(argv[1]); - - if (tagType < 0) { - command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); - return COMMAND_RETURN_ERROR; - } - - if (tagType == LOCATE_TAG_ANY_TYPE) { - command_error(client, ACK_ERROR_ARG, - "\"any\" is not a valid return tag type"); - return COMMAND_RETURN_ERROR; - } - - /* for compatibility with < 0.12.0 */ - if (argc == 3) { - if (tagType != TAG_ALBUM) { - command_error(client, ACK_ERROR_ARG, - "should be \"%s\" for 3 arguments", - 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_ARTIST; - conditionals->items[0].needle = g_strdup(argv[2]); - } else { - conditionals = - locate_item_list_parse(argv + 2, argc - 2); - if (conditionals == NULL) { - command_error(client, ACK_ERROR_ARG, - "not able to parse args"); - return COMMAND_RETURN_ERROR; - } - } - - GError *error = NULL; - enum command_return ret = - listAllUniqueTags(client, tagType, conditionals, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); - - locate_item_list_free(conditionals); - - return ret; -} - -static enum command_return -handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned start, end; - int to; - enum playlist_result result; - - if (!check_range(client, &start, &end, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_range(&g_playlist, client->player_control, - start, end, to); - return print_playlist_result(client, result); -} - -static enum command_return -handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id; - int to; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, client->player_control, - id, to); - return print_playlist_result(client, result); -} - -static enum command_return -handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned song1, song2; - enum playlist_result result; - - if (!check_unsigned(client, &song1, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &song2, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_swap_songs(&g_playlist, client->player_control, - song1, song2); - return print_playlist_result(client, result); -} - -static enum command_return -handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id1, id2; - enum playlist_result result; - - if (!check_unsigned(client, &id1, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &id2, argv[2])) - return COMMAND_RETURN_ERROR; - result = playlist_swap_songs_id(&g_playlist, client->player_control, - id1, id2); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned song, seek_time; - enum playlist_result result; - - if (!check_unsigned(client, &song, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) - return COMMAND_RETURN_ERROR; - - result = playlist_seek_song(&g_playlist, client->player_control, - song, seek_time); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned id, seek_time; - enum playlist_result result; - - if (!check_unsigned(client, &id, argv[1])) - return COMMAND_RETURN_ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) - return COMMAND_RETURN_ERROR; - - result = playlist_seek_song_id(&g_playlist, client->player_control, - id, seek_time); - return print_playlist_result(client, result); -} - -static enum command_return -handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *p = argv[1]; - bool relative = *p == '+' || *p == '-'; - int seek_time; - if (!check_int(client, &seek_time, p)) - return COMMAND_RETURN_ERROR; - - enum playlist_result result = - playlist_seek_current(&g_playlist, client->player_control, - seek_time, relative); - return print_playlist_result(client, result); -} - -static enum command_return -handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; - - GError *error = NULL; - return printInfoForAllIn(client, directory, &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_ping(G_GNUC_UNUSED struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_password(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned permission = 0; - - if (getPermissionFromPassword(argv[1], &permission) < 0) { - command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); - return COMMAND_RETURN_ERROR; - } - - client_set_permission(client, permission); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned xfade_time; - - if (!check_unsigned(client, &xfade_time, argv[1])) - return COMMAND_RETURN_ERROR; - pc_set_cross_fade(client->player_control, 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(client->player_control, 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(client->player_control, delay_secs); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned device; - bool ret; - - if (!check_unsigned(client, &device, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = audio_output_enable_index(device); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - unsigned device; - bool ret; - - if (!check_unsigned(client, &device, argv[1])) - return COMMAND_RETURN_ERROR; - - ret = audio_output_disable_index(device); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_devices(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - printAudioDevices(client); - - return COMMAND_RETURN_OK; -} - -/* don't be fooled, this is the command handler for "commands" command */ -static enum command_return -handle_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); - -static enum command_return -handle_not_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); - -static enum command_return -handle_config(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - if (!client_is_local(client)) { - command_error(client, ACK_ERROR_PERMISSION, - "Command only permitted to local clients"); - return COMMAND_RETURN_ERROR; - } - - const char *path = mapper_get_music_directory_utf8(); - if (path != NULL) - client_printf(client, "music_directory: %s\n", path); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - GError *error = NULL; - return spl_clear(argv[1], &error) - ? COMMAND_RETURN_OK - : print_error(client, error); -} - -static enum command_return -handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - char *playlist = argv[1]; - char *uri = argv[2]; - - bool success; - GError *error = NULL; - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return COMMAND_RETURN_ERROR; - } - - success = spl_append_uri(uri, playlist, &error); - } else - success = addAllInToStoredPlaylist(uri, playlist, &error); - - if (!success && error == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return COMMAND_RETURN_ERROR; - } - - return success ? COMMAND_RETURN_OK : print_error(client, error); -} - -static enum command_return -handle_listplaylists(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - GError *error = NULL; - GPtrArray *list = spl_list(&error); - if (list == NULL) - return print_error(client, error); - - print_spl_list(client, list); - spl_list_free(list); - return COMMAND_RETURN_OK; -} - -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[]) -{ - unsigned flags = 0, j; - int i; - const char *const* idle_names; - - idle_names = idle_get_names(); - for (i = 1; i < argc; ++i) { - if (!argv[i]) - continue; - - for (j = 0; idle_names[j]; ++j) { - if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { - flags |= (1 << j); - } - } - } - - /* No argument means that the client wants to receive everything */ - if (flags == 0) - flags = ~0; - - /* enable "idle" mode on this client */ - client_idle_wait(client, flags); - - /* return value is "1" so the caller won't print "OK" */ - return 1; -} - -#ifdef ENABLE_SQLITE -struct sticker_song_find_data { - struct client *client; - const char *name; -}; - -static void -sticker_song_find_print_cb(struct song *song, const char *value, - gpointer user_data) -{ - struct sticker_song_find_data *data = user_data; - - 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; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - value = sticker_song_get_value(song, argv[4]); - if (value == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such sticker"); - return COMMAND_RETURN_ERROR; - } - - sticker_print_value(client, argv[4], value); - 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; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - sticker = sticker_song_get(song); - if (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; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - ret = sticker_song_set_value(song, argv[4], argv[5]); - if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set sticker value"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - /* delete song song_id [key] */ - } else if ((argc == 4 || argc == 5) && - strcmp(argv[1], "delete") == 0) { - struct song *song; - bool ret; - - song = db_get_song(argv[3]); - if (song == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such song"); - return COMMAND_RETURN_ERROR; - } - - ret = argc == 4 - ? sticker_song_delete(song) - : sticker_song_delete_value(song, argv[4]); - if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "no such sticker"); - return COMMAND_RETURN_ERROR; - } - - 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; - bool success; - struct sticker_song_find_data data = { - .client = client, - .name = argv[4], - }; - - db_lock(); - directory = db_get_directory(argv[3]); - if (directory == NULL) { - db_unlock(); - command_error(client, ACK_ERROR_NO_EXIST, - "no such directory"); - return COMMAND_RETURN_ERROR; - } - - success = sticker_song_find(directory, data.name, - sticker_song_find_print_cb, &data); - db_unlock(); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set search sticker database"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; - } else { - command_error(client, ACK_ERROR_ARG, "bad request"); - return COMMAND_RETURN_ERROR; - } -} - -static enum command_return -handle_sticker(struct client *client, int argc, char *argv[]) -{ - assert(argc >= 4); - - if (!sticker_enabled()) { - command_error(client, ACK_ERROR_UNKNOWN, - "sticker database is disabled"); - return COMMAND_RETURN_ERROR; - } - - if (strcmp(argv[2], "song") == 0) - return handle_sticker_song(client, argc, argv); - else { - command_error(client, ACK_ERROR_ARG, - "unknown sticker domain"); - return COMMAND_RETURN_ERROR; - } -} -#endif - -static enum command_return -handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - assert(argc == 2); - - switch (client_subscribe(client, argv[1])) { - case CLIENT_SUBSCRIBE_OK: - return COMMAND_RETURN_OK; - - case CLIENT_SUBSCRIBE_INVALID: - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); - return COMMAND_RETURN_ERROR; - - case CLIENT_SUBSCRIBE_ALREADY: - command_error(client, ACK_ERROR_EXIST, - "already subscribed to this channel"); - return COMMAND_RETURN_ERROR; - - case CLIENT_SUBSCRIBE_FULL: - command_error(client, ACK_ERROR_EXIST, - "subscription list is full"); - return COMMAND_RETURN_ERROR; - } - - /* unreachable */ - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - assert(argc == 2); - - if (client_unsubscribe(client, argv[1])) - return COMMAND_RETURN_OK; - else { - command_error(client, ACK_ERROR_NO_EXIST, - "not subscribed to this channel"); - return COMMAND_RETURN_ERROR; - } -} - -struct channels_context { - GStringChunk *chunk; - - GHashTable *channels; -}; - -static void -collect_channels(gpointer data, gpointer user_data) -{ - struct channels_context *context = user_data; - const struct client *client = data; - - for (GSList *i = client->subscriptions; i != NULL; - i = g_slist_next(i)) { - const char *channel = i->data; - - if (g_hash_table_lookup(context->channels, channel) == NULL) { - char *channel2 = g_string_chunk_insert(context->chunk, - channel); - g_hash_table_insert(context->channels, channel2, - context); - } - } -} - -static void -print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) -{ - struct client *client = user_data; - const char *channel = key; - - client_printf(client, "channel: %s\n", channel); -} - -static enum command_return -handle_channels(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 1); - - struct channels_context context = { - .chunk = g_string_chunk_new(1024), - .channels = g_hash_table_new(g_str_hash, g_str_equal), - }; - - client_list_foreach(collect_channels, &context); - - g_hash_table_foreach(context.channels, print_channel, client); - - g_hash_table_destroy(context.channels); - g_string_chunk_free(context.chunk); - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_read_messages(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 1); - - GSList *messages = client_read_messages(client); - - for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { - struct client_message *msg = i->data; - - client_printf(client, "channel: %s\nmessage: %s\n", - msg->channel, msg->message); - client_message_free(msg); - } - - g_slist_free(messages); - - return COMMAND_RETURN_OK; -} - -struct send_message_context { - struct client_message msg; - - bool sent; -}; - -static void -send_message(gpointer data, gpointer user_data) -{ - struct send_message_context *context = user_data; - struct client *client = data; - - if (client_push_message(client, &context->msg)) - context->sent = true; -} - -static enum command_return -handle_send_message(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - assert(argc == 3); - - if (!client_message_valid_channel_name(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); - return COMMAND_RETURN_ERROR; - } - - struct send_message_context context = { - .sent = false, - }; - - client_message_init(&context.msg, argv[1], argv[2]); - - client_list_foreach(send_message, &context); - - client_message_deinit(&context.msg); - - if (context.sent) - return COMMAND_RETURN_OK; - else { - command_error(client, ACK_ERROR_NO_EXIST, - "nobody is subscribed to this channel"); - return COMMAND_RETURN_ERROR; - } -} - -/** - * The command registry. - * - * This array must be sorted! - */ -static const struct command commands[] = { - { "add", PERMISSION_ADD, 1, 1, handle_add }, - { "addid", PERMISSION_ADD, 1, 2, handle_addid }, - { "channels", PERMISSION_READ, 0, 0, handle_channels }, - { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, - { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, - { "close", PERMISSION_NONE, -1, -1, handle_close }, - { "commands", PERMISSION_NONE, 0, 0, handle_commands }, - { "config", PERMISSION_ADMIN, 0, 0, handle_config }, - { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, - { "count", PERMISSION_READ, 2, -1, handle_count }, - { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, - { "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 }, - { "listall", PERMISSION_READ, 0, 1, handle_listall }, - { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, - { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, - { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, - { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, - { "load", PERMISSION_ADD, 1, 2, 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 }, - { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, - { "outputs", PERMISSION_READ, 0, 0, handle_devices }, - { "password", PERMISSION_NONE, 1, 1, handle_password }, - { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, - { "ping", PERMISSION_NONE, 0, 0, handle_ping }, - { "play", PERMISSION_CONTROL, 0, 1, handle_play }, - { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, - { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, - { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, - { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, - { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, - { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, - { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, - { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, - { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, - { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, - { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, - { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, - { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, - { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, - { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, - { "random", PERMISSION_CONTROL, 1, 1, handle_random }, - { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, - { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, - { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, - { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, - handle_replay_gain_mode }, - { "replay_gain_status", PERMISSION_READ, 0, 0, - handle_replay_gain_status }, - { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, - { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, - { "save", PERMISSION_CONTROL, 1, 1, handle_save }, - { "search", PERMISSION_READ, 2, -1, handle_search }, - { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, - { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, - { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, - { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, - { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, - { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, - { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, - { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, - { "single", PERMISSION_CONTROL, 1, 1, handle_single }, - { "stats", PERMISSION_READ, 0, 0, handle_stats }, - { "status", PERMISSION_READ, 0, 0, handle_status }, -#ifdef ENABLE_SQLITE - { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, -#endif - { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, - { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, - { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, - { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, - { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, - { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, - { "update", PERMISSION_CONTROL, 0, 1, handle_update }, - { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, -}; - -static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); - -static bool -command_available(G_GNUC_UNUSED const struct command *cmd) -{ -#ifdef ENABLE_SQLITE - if (strcmp(cmd->cmd, "sticker") == 0) - return sticker_enabled(); -#endif - - return true; -} - -/* don't be fooled, this is the command handler for "commands" command */ -static enum command_return -handle_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const unsigned permission = client_get_permission(client); - const struct command *cmd; - - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; - - if (cmd->permission == (permission & cmd->permission) && - command_available(cmd)) - client_printf(client, "command: %s\n", cmd->cmd); - } - - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_not_commands(struct client *client, - G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) -{ - const unsigned permission = client_get_permission(client); - const struct command *cmd; - - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; - - if (cmd->permission != (permission & cmd->permission)) - client_printf(client, "command: %s\n", cmd->cmd); - } - - return COMMAND_RETURN_OK; -} - -void command_init(void) -{ -#ifndef NDEBUG - /* ensure that the command list is sorted */ - for (unsigned i = 0; i < num_commands - 1; ++i) - assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); -#endif -} - -void command_finish(void) -{ -} - -static const struct command * -command_lookup(const char *name) -{ - unsigned a = 0, b = num_commands, i; - int cmp; - - /* binary search */ - do { - i = (a + b) / 2; - - cmp = strcmp(name, commands[i].cmd); - if (cmp == 0) - return &commands[i]; - else if (cmp < 0) - b = i; - else if (cmp > 0) - a = i + 1; - } while (a < b); - - return NULL; -} - -static bool -command_check_request(const struct command *cmd, struct client *client, - unsigned permission, int argc, char *argv[]) -{ - int min = cmd->min + 1; - int max = cmd->max + 1; - - if (cmd->permission != (permission & cmd->permission)) { - if (client != NULL) - command_error(client, ACK_ERROR_PERMISSION, - "you don't have permission for \"%s\"", - cmd->cmd); - return false; - } - - if (min == 0) - return true; - - if (min == max && max != argc) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "wrong number of arguments for \"%s\"", - argv[0]); - return false; - } else if (argc < min) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "too few arguments for \"%s\"", argv[0]); - return false; - } else if (argc > max && max /* != 0 */ ) { - if (client != NULL) - command_error(client, ACK_ERROR_ARG, - "too many arguments for \"%s\"", argv[0]); - return false; - } else - return true; -} - -static const struct command * -command_checked_lookup(struct client *client, unsigned permission, - int argc, char *argv[]) -{ - const struct command *cmd; - - current_command = ""; - - if (argc == 0) - return NULL; - - cmd = command_lookup(argv[0]); - if (cmd == NULL) { - if (client != NULL) - command_error(client, ACK_ERROR_UNKNOWN, - "unknown command \"%s\"", argv[0]); - return NULL; - } - - current_command = cmd->cmd; - - if (!command_check_request(cmd, client, permission, argc, argv)) - return NULL; - - return cmd; -} - -enum command_return -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; - - command_list_num = num; - - /* get the command name (first word on the line) */ - - 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 COMMAND_RETURN_ERROR; - } - - argc = 1; - - /* now parse the arguments (quoted or unquoted) */ - - while (argc < (int)G_N_ELEMENTS(argv) && - (argv[argc] = - tokenizer_next_param(&line, &error)) != NULL) - ++argc; - - /* some error checks; we have to set current_command because - command_error() expects it to be set */ - - 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 68d1f95e4..9ea5bb52f 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,27 +20,34 @@ #ifndef MPD_COMMAND_H #define MPD_COMMAND_H -#include "ack.h" - -#include <glib.h> -#include <stdbool.h> - enum command_return { - COMMAND_RETURN_ERROR = -1, - COMMAND_RETURN_OK = 0, - COMMAND_RETURN_KILL = 10, - COMMAND_RETURN_CLOSE = 20, + /** + * The command has succeeded, but the "OK" response was not + * yet sent to the client. + */ + COMMAND_RETURN_OK, + + /** + * The connection is now in "idle" mode, and no response shall + * be generated. + */ + COMMAND_RETURN_IDLE, + + /** + * There was an error. The "ACK" response was sent to the + * client. + */ + COMMAND_RETURN_ERROR, + + /** + * The connection to this client shall be closed. + */ + COMMAND_RETURN_CLOSE, + + /** + * The MPD process shall be shut down. + */ + COMMAND_RETURN_KILL, }; -struct client; - -void command_init(void); - -void command_finish(void); - -enum command_return -command_process(struct client *client, unsigned num, char *line); - -void command_success(struct client *client); - #endif diff --git a/src/conf.c b/src/conf.c deleted file mode 100644 index a22f2ec91..000000000 --- a/src/conf.c +++ /dev/null @@ -1,666 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "utils.h" -#include "string_util.h" -#include "tokenizer.h" -#include "path.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "config" - -#define MAX_STRING_SIZE MPD_PATH_MAX+80 - -#define CONF_COMMENT '#' - -struct config_entry { - const char *const name; - const bool repeatable; - const bool block; - - GSList *params; -}; - -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 = "restore_paused", 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 = CONF_DESPOTIFY_USER, false, false }, - { .name = CONF_DESPOTIFY_PASSWORD, false, false}, - { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, - { .name = "filter", true, true }, -}; - -static bool -get_bool(const char *value, bool *value_r) -{ - static const char *t[] = { "yes", "true", "1", NULL }; - static const char *f[] = { "no", "false", "0", NULL }; - - if (string_array_contains(t, value)) { - *value_r = true; - return true; - } - - if (string_array_contains(f, value)) { - *value_r = false; - return true; - } - - return false; -} - -struct config_param * -config_new_param(const char *value, int line) -{ - struct config_param *ret = g_new(struct config_param, 1); - - if (!value) - ret->value = NULL; - else - ret->value = g_strdup(value); - - ret->line = line; - - ret->num_block_params = 0; - ret->block_params = NULL; - ret->used = false; - - return ret; -} - -void -config_param_free(struct config_param *param) -{ - g_free(param->value); - - for (unsigned i = 0; i < param->num_block_params; i++) { - g_free(param->block_params[i].name); - g_free(param->block_params[i].value); - } - - if (param->num_block_params) - g_free(param->block_params); - - g_free(param); -} - -static void -config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct config_param *param = data; - - config_param_free(param); -} - -static struct config_entry * -config_entry_get(const char *name) -{ - 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; - } - - return NULL; -} - -void config_global_finish(void) -{ - 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_free_callback, NULL); - g_slist_free(entry->params); - } -} - -void config_global_init(void) -{ -} - -static void -config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - 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 = ¶m->block_params[i]; - - if (!bp->used) - g_warning("option '%s' on line %i was not recognized", - bp->name, bp->line); - } -} - -void config_global_check(void) -{ - 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 -config_add_block_param(struct config_param * param, const char *name, - const char *value, int line) -{ - struct block_param *bp; - - assert(config_get_block_param(param, name) == NULL); - - param->num_block_params++; - - param->block_params = g_realloc(param->block_params, - param->num_block_params * - sizeof(param->block_params[0])); - - bp = ¶m->block_params[param->num_block_params - 1]; - - bp->name = g_strdup(name); - bp->value = g_strdup(value); - bp->line = line; - bp->used = false; -} - -static bool -config_read_name_value(struct config_param *param, char *input, unsigned line, - GError **error_r) -{ - const char *name = tokenizer_next_word(&input, error_r); - if (name == NULL) { - assert(*input != 0); - return false; - } - - const char *value = tokenizer_next_string(&input, error_r); - if (value == NULL) { - if (*input == 0) { - assert(error_r == NULL || *error_r == NULL); - g_set_error(error_r, config_quark(), 0, - "Value missing"); - } else { - assert(error_r == NULL || *error_r != NULL); - } - - return false; - } - - if (*input != 0 && *input != CONF_COMMENT) { - g_set_error(error_r, config_quark(), 0, - "Unknown tokens after value"); - return false; - } - - const struct block_param *bp = config_get_block_param(param, name); - if (bp != NULL) { - g_set_error(error_r, config_quark(), 0, - "\"%s\" is duplicate, first defined on line %i", - name, bp->line); - return false; - } - - config_add_block_param(param, name, value, line); - return true; -} - -static struct config_param * -config_read_block(FILE *fp, int *count, char *string, GError **error_r) -{ - struct config_param *ret = config_new_param(NULL, *count); - GError *error = NULL; - - while (true) { - char *line; - - 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 = strchug_fast(line); - if (*line == 0 || *line == CONF_COMMENT) - continue; - - if (*line == '}') { - /* end of this block; return from the function - (and from this "while" loop) */ - - line = strchug_fast(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 NULL; - } - - return ret; - } - - /* parse name and value */ - - if (!config_read_name_value(ret, line, *count, &error)) { - assert(*line != 0); - config_param_free(ret); - g_propagate_prefixed_error(error_r, error, - "line %i: ", *count); - return NULL; - } - } -} - -bool -config_read_file(const char *file, GError **error_r) -{ - FILE *fp; - char string[MAX_STRING_SIZE + 1]; - int count = 0; - struct config_entry *entry; - struct config_param *param; - - g_debug("loading file %s", file); - - if (!(fp = fopen(file, "r"))) { - g_set_error(error_r, config_quark(), errno, - "Failed to open %s: %s", - file, g_strerror(errno)); - return false; - } - - while (fgets(string, MAX_STRING_SIZE, fp)) { - char *line; - const char *name, *value; - GError *error = NULL; - - count++; - - line = strchug_fast(string); - if (*line == 0 || *line == CONF_COMMENT) - continue; - - /* the first token in each line is the name, followed - by either the value or '{' */ - - name = tokenizer_next_word(&line, &error); - if (name == NULL) { - assert(*line != 0); - g_propagate_prefixed_error(error_r, error, - "line %i: ", count); - fclose(fp); - return false; - } - - /* get the definition of that option, and check the - "repeatable" flag */ - - 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); - fclose(fp); - return false; - } - - 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); - fclose(fp); - return false; - } - - /* now parse the block or the value */ - - 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); - fclose(fp); - return false; - } - - line = strchug_fast(line + 1); - if (*line != 0 && *line != CONF_COMMENT) { - g_set_error(error_r, config_quark(), 0, - "line %i: Unknown tokens after '{'", - count); - fclose(fp); - return false; - } - - param = config_read_block(fp, &count, string, error_r); - if (param == NULL) { - fclose(fp); - 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); - } - - fclose(fp); - return false; - } - - if (*line != 0 && *line != CONF_COMMENT) { - g_set_error(error_r, config_quark(), 0, - "line %i: Unknown tokens after value", - count); - fclose(fp); - return false; - } - - param = config_new_param(value, count); - } - - entry->params = g_slist_append(entry->params, param); - } - fclose(fp); - - return true; -} - -const struct config_param * -config_get_next_param(const char *name, const struct config_param * last) -{ - struct config_entry *entry; - GSList *node; - struct config_param *param; - - entry = config_entry_get(name); - if (entry == NULL) - return NULL; - - node = entry->params; - - if (last) { - node = g_slist_find(node, last); - if (node == NULL) - return NULL; - - node = g_slist_next(node); - } - - if (node == NULL) - return NULL; - - param = node->data; - param->used = true; - return param; -} - -const char * -config_get_string(const char *name, const char *default_value) -{ - const struct config_param *param = config_get_param(name); - - if (param == NULL) - return default_value; - - return param->value; -} - -char * -config_dup_path(const char *name, GError **error_r) -{ - assert(error_r != NULL); - assert(*error_r == NULL); - - const struct config_param *param = config_get_param(name); - if (param == NULL) - return NULL; - - char *path = parsePath(param->value, error_r); - if (G_UNLIKELY(path == NULL)) - g_prefix_error(error_r, - "Invalid path in \"%s\" at line %i: ", - name, param->line); - - return 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); - long value; - char *endptr; - - if (param == NULL) - return default_value; - - value = strtol(param->value, &endptr, 0); - if (*endptr != 0) - MPD_ERROR("Not a valid number in line %i", param->line); - - if (value <= 0) - MPD_ERROR("Not a positive number in line %i", param->line); - - return (unsigned)value; -} - -const struct block_param * -config_get_block_param(const struct config_param * param, const char *name) -{ - if (param == NULL) - return NULL; - - for (unsigned i = 0; i < param->num_block_params; i++) { - if (0 == strcmp(name, param->block_params[i].name)) { - struct block_param *bp = ¶m->block_params[i]; - bp->used = true; - return bp; - } - } - - return NULL; -} - -bool config_get_bool(const char *name, bool default_value) -{ - const struct config_param *param = config_get_param(name); - bool success, value; - - if (param == NULL) - 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; -} - -const char * -config_get_block_string(const struct config_param *param, const char *name, - const char *default_value) -{ - const struct block_param *bp = config_get_block_param(param, name); - - if (bp == NULL) - return default_value; - - return bp->value; -} - -char * -config_dup_block_path(const struct config_param *param, const char *name, - GError **error_r) -{ - assert(error_r != NULL); - assert(*error_r == NULL); - - const struct block_param *bp = config_get_block_param(param, name); - if (bp == NULL) - return NULL; - - char *path = parsePath(bp->value, error_r); - if (G_UNLIKELY(path == NULL)) - g_prefix_error(error_r, - "Invalid path in \"%s\" at line %i: ", - name, bp->line); - - return path; -} - -unsigned -config_get_block_unsigned(const struct config_param *param, const char *name, - unsigned default_value) -{ - const struct block_param *bp = config_get_block_param(param, name); - long value; - char *endptr; - - if (bp == NULL) - return default_value; - - value = strtol(bp->value, &endptr, 0); - if (*endptr != 0) - MPD_ERROR("Not a valid number in line %i", bp->line); - - if (value < 0) - MPD_ERROR("Not a positive number in line %i", bp->line); - - return (unsigned)value; -} - -bool -config_get_block_bool(const struct config_param *param, const char *name, - bool default_value) -{ - const struct block_param *bp = config_get_block_param(param, name); - bool success, value; - - if (bp == NULL) - 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; -} diff --git a/src/conf.h b/src/conf.h deleted file mode 100644 index 815c739b1..000000000 --- a/src/conf.h +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONF_H -#define MPD_CONF_H - -#include <stdbool.h> -#include <glib.h> - -#define CONF_MUSIC_DIR "music_directory" -#define CONF_PLAYLIST_DIR "playlist_directory" -#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_LOG_FILE "log_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_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_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" -#define CONF_BUFFER_BEFORE_PLAY "buffer_before_play" -#define CONF_HTTP_PROXY_HOST "http_proxy_host" -#define CONF_HTTP_PROXY_PORT "http_proxy_port" -#define CONF_HTTP_PROXY_USER "http_proxy_user" -#define CONF_HTTP_PROXY_PASSWORD "http_proxy_password" -#define CONF_CONN_TIMEOUT "connection_timeout" -#define CONF_MAX_CONN "max_connections" -#define CONF_MAX_PLAYLIST_LENGTH "max_playlist_length" -#define CONF_MAX_COMMAND_LIST_SIZE "max_command_list_size" -#define CONF_MAX_OUTPUT_BUFFER_SIZE "max_output_buffer_size" -#define CONF_FS_CHARSET "filesystem_charset" -#define CONF_ID3V1_ENCODING "id3v1_encoding" -#define CONF_METADATA_TO_USE "metadata_to_use" -#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" -#define CONF_DECODER "decoder" -#define CONF_INPUT "input" -#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" -#define CONF_PLAYLIST_PLUGIN "playlist_plugin" -#define CONF_AUTO_UPDATE "auto_update" -#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" -#define CONF_DESPOTIFY_USER "despotify_user" -#define CONF_DESPOTIFY_PASSWORD "despotify_password" -#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate" - -#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) -#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false - -#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 { - char *value; - unsigned int line; - - 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. - */ -G_GNUC_CONST -static inline GQuark -config_quark(void) -{ - return g_quark_from_static_string("config"); -} - -void config_global_init(void); -void config_global_finish(void); - -/** - * Call this function after all configuration has been evaluated. It - * checks for unused parameters, and logs warnings. - */ -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 -const struct config_param * -config_get_next_param(const char *name, const struct config_param *last); - -G_GNUC_PURE -static inline const 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); - -/** - * Returns an optional configuration variable which contains an - * absolute path. If there is a tilde prefix, it is expanded. - * Returns NULL if the value is not present. If the path could not be - * parsed, returns NULL and sets the error. - * - * The return value must be freed with g_free(). - */ -G_GNUC_MALLOC -char * -config_dup_path(const char *name, GError **error_r); - -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 -const 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); - -G_GNUC_MALLOC -static inline char * -config_dup_block_string(const struct config_param *param, const char *name, - const char *default_value) -{ - return g_strdup(config_get_block_string(param, name, default_value)); -} - -/** - * Same as config_dup_path(), but looks up the setting in the - * specified block. - */ -G_GNUC_MALLOC -char * -config_dup_block_path(const struct config_param *param, const char *name, - GError **error_r); - -G_GNUC_PURE -unsigned -config_get_block_unsigned(const struct config_param *param, const char *name, - unsigned default_value); - -G_GNUC_PURE -bool -config_get_block_bool(const struct config_param *param, const char *name, - bool default_value); - -G_GNUC_MALLOC -struct config_param * -config_new_param(const char *value, int line); - -void -config_param_free(struct config_param *param); - -void -config_add_block_param(struct config_param * param, const char *name, - const char *value, int line); - -#endif diff --git a/src/crossfade.c b/src/crossfade.c deleted file mode 100644 index 46a0dff30..000000000 --- a/src/crossfade.c +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "crossfade.h" -#include "chunk.h" -#include "audio_format.h" -#include "tag.h" - -#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 = 0; - float chunks_f; - float mixramp_overlap; - - 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(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) { - chunks = max_chunks; - g_warning("audio_buffer_size too small for computed MixRamp overlap"); - } - - return chunks; -} diff --git a/src/crossfade.h b/src/crossfade.h deleted file mode 100644 index d581dbfe0..000000000 --- a/src/crossfade.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CROSSFADE_H -#define MPD_CROSSFADE_H - -struct audio_format; -struct music_chunk; - -/** - * Calculate how many music pipe chunks should be used for crossfading. - * - * @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 - * @return the number of chunks for crossfading, or 0 if cross fading - * 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); - -#endif diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx new file mode 100644 index 000000000..584a77a81 --- /dev/null +++ b/src/cue/CueParser.cxx @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CueParser.hxx" +#include "util/StringUtil.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +CueParser::CueParser() + :state(HEADER), tag(new Tag()), + filename(nullptr), + current(nullptr), + previous(nullptr), + finished(nullptr), + end(false) {} + +CueParser::~CueParser() +{ + delete tag; + g_free(filename); + + if (current != nullptr) + current->Free(); + + if (previous != nullptr) + previous->Free(); + + if (finished != nullptr) + finished->Free(); +} + +static const char * +cue_next_word(char *p, char **pp) +{ + assert(p >= *pp); + assert(!g_ascii_isspace(*p)); + + const char *word = p; + while (*p != 0 && !g_ascii_isspace(*p)) + ++p; + + *p = 0; + *pp = p + 1; + return word; +} + +static const char * +cue_next_quoted(char *p, char **pp) +{ + assert(p >= *pp); + assert(p[-1] == '"'); + + char *end = strchr(p, '"'); + if (end == nullptr) { + /* syntax error - ignore it silently */ + *pp = p + strlen(p); + return p; + } + + *end = 0; + *pp = end + 1; + + return p; +} + +static const char * +cue_next_token(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return nullptr; + + return cue_next_word(p, pp); +} + +static const char * +cue_next_value(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return nullptr; + + if (*p == '"') + return cue_next_quoted(p + 1, pp); + else + return cue_next_word(p, pp); +} + +static void +cue_add_tag(Tag &tag, enum tag_type type, char *p) +{ + const char *value = cue_next_value(&p); + if (value != nullptr) + tag.AddItem(type, value); + +} + +static void +cue_parse_rem(char *p, Tag &tag) +{ + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + enum tag_type type2 = tag_name_parse_i(type); + if (type2 != TAG_NUM_OF_ITEM_TYPES) + cue_add_tag(tag, type2, p); +} + +Tag * +CueParser::GetCurrentTag() +{ + if (state == HEADER) + return tag; + else if (state == TRACK) + return current->tag; + else + return nullptr; +} + +static int +cue_parse_position(const char *p) +{ + char *endptr; + unsigned long minutes = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long seconds = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long frames = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != 0) + return -1; + + return minutes * 60000 + seconds * 1000 + frames * 1000 / 75; +} + +void +CueParser::Commit() +{ + /* the caller of this library must call cue_parser_get() often + enough */ + assert(finished == nullptr); + assert(!end); + + if (current == nullptr) + return; + + finished = previous; + previous = current; + current = nullptr; +} + +void +CueParser::Feed2(char *p) +{ + assert(!end); + assert(p != nullptr); + + const char *command = cue_next_token(&p); + if (command == nullptr) + return; + + if (strcmp(command, "REM") == 0) { + Tag *current_tag = GetCurrentTag(); + if (current_tag != nullptr) + cue_parse_rem(p, *current_tag); + } else if (strcmp(command, "PERFORMER") == 0) { + /* MPD knows a "performer" tag, but it is not a good + match for this CUE tag; from the Hydrogenaudio + Knowledgebase: "At top-level this will specify the + CD artist, while at track-level it specifies the + track artist." */ + + enum tag_type type = state == TRACK + ? TAG_ARTIST + : TAG_ALBUM_ARTIST; + + Tag *current_tag = GetCurrentTag(); + if (current_tag != nullptr) + cue_add_tag(*current_tag, type, p); + } else if (strcmp(command, "TITLE") == 0) { + if (state == HEADER) + cue_add_tag(*tag, TAG_ALBUM, p); + else if (state == TRACK) + cue_add_tag(*current->tag, TAG_TITLE, p); + } else if (strcmp(command, "FILE") == 0) { + Commit(); + + const char *new_filename = cue_next_value(&p); + if (new_filename == nullptr) + return; + + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + if (strcmp(type, "WAVE") != 0 && + strcmp(type, "MP3") != 0 && + strcmp(type, "AIFF") != 0) { + state = IGNORE_FILE; + return; + } + + state = WAVE; + g_free(filename); + filename = g_strdup(new_filename); + } else if (state == IGNORE_FILE) { + return; + } else if (strcmp(command, "TRACK") == 0) { + Commit(); + + const char *nr = cue_next_token(&p); + if (nr == nullptr) + return; + + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + if (strcmp(type, "AUDIO") != 0) { + state = IGNORE_TRACK; + return; + } + + state = TRACK; + current = Song::NewRemote(filename); + assert(current->tag == nullptr); + current->tag = new Tag(*tag); + current->tag->AddItem(TAG_TRACK, nr); + last_updated = false; + } else if (state == IGNORE_TRACK) { + return; + } else if (state == TRACK && strcmp(command, "INDEX") == 0) { + const char *nr = cue_next_token(&p); + if (nr == nullptr) + return; + + const char *position = cue_next_token(&p); + if (position == nullptr) + return; + + int position_ms = cue_parse_position(position); + if (position_ms < 0) + return; + + if (!last_updated && previous != nullptr && + previous->start_ms < (unsigned)position_ms) { + last_updated = true; + previous->end_ms = position_ms; + previous->tag->time = + (previous->end_ms - previous->start_ms + 500) / 1000; + } + + current->start_ms = position_ms; + } +} + +void +CueParser::Feed(const char *line) +{ + assert(!end); + assert(line != nullptr); + + char *allocated = g_strdup(line); + Feed2(allocated); + g_free(allocated); +} + +void +CueParser::Finish() +{ + if (end) + /* has already been called, ignore */ + return; + + Commit(); + end = true; +} + +Song * +CueParser::Get() +{ + if (finished == nullptr && end) { + /* cue_parser_finish() has been called already: + deliver all remaining (partial) results */ + assert(current == nullptr); + + finished = previous; + previous = nullptr; + } + + Song *song = finished; + finished = nullptr; + return song; +} diff --git a/src/cue/CueParser.hxx b/src/cue/CueParser.hxx new file mode 100644 index 000000000..5cb51200f --- /dev/null +++ b/src/cue/CueParser.hxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CUE_PARSER_HXX +#define MPD_CUE_PARSER_HXX + +#include "check.h" +#include "gcc.h" + +struct Song; +struct Tag; + +class CueParser { + enum { + /** + * Parsing the CUE header. + */ + HEADER, + + /** + * Parsing a "FILE ... WAVE". + */ + WAVE, + + /** + * Ignore everything until the next "FILE". + */ + IGNORE_FILE, + + /** + * Parsing a "TRACK ... AUDIO". + */ + TRACK, + + /** + * Ignore everything until the next "TRACK". + */ + IGNORE_TRACK, + } state; + + Tag *tag; + + char *filename; + + /** + * The song currently being edited. + */ + Song *current; + + /** + * The previous song. It is remembered because its end_time + * will be set to the current song's start time. + */ + Song *previous; + + /** + * A song that is completely finished and can be returned to + * the caller via cue_parser_get(). + */ + Song *finished; + + /** + * Set to true after previous.end_time has been updated to the + * start time of the current song. + */ + bool last_updated; + + /** + * Tracks whether cue_parser_finish() has been called. If + * true, then all remaining (partial) results will be + * delivered by cue_parser_get(). + */ + bool end; + +public: + CueParser(); + ~CueParser(); + + /** + * Feed a text line from the CUE file into the parser. Call + * cue_parser_get() after this to see if a song has been finished. + */ + void Feed(const char *line); + + /** + * Tell the parser that the end of the file has been reached. Call + * cue_parser_get() after this to see if a song has been finished. + * This procedure must be done twice! + */ + void Finish(); + + /** + * Check if a song was finished by the last cue_parser_feed() or + * cue_parser_finish() call. + * + * @return a song object that must be freed by the caller, or NULL if + * no song was finished at this time + */ + Song *Get(); + +private: + gcc_pure + Tag *GetCurrentTag(); + + /** + * Commit the current song. It will be moved to "previous", + * so the next song may soon edit its end time (using the next + * song's start time). + */ + void Commit(); + + void Feed2(char *p); +}; + +#endif diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c deleted file mode 100644 index 9ccc3bcdd..000000000 --- a/src/cue/cue_parser.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "cue_parser.h" -#include "string_util.h" -#include "song.h" -#include "tag.h" - -#include <assert.h> -#include <stdlib.h> - -struct cue_parser { - enum { - /** - * Parsing the CUE header. - */ - HEADER, - - /** - * Parsing a "FILE ... WAVE". - */ - WAVE, - - /** - * Ignore everything until the next "FILE". - */ - IGNORE_FILE, - - /** - * Parsing a "TRACK ... AUDIO". - */ - TRACK, - - /** - * Ignore everything until the next "TRACK". - */ - IGNORE_TRACK, - } state; - - struct tag *tag; - - char *filename; - - /** - * The song currently being edited. - */ - struct song *current; - - /** - * The previous song. It is remembered because its end_time - * will be set to the current song's start time. - */ - struct song *previous; - - /** - * A song that is completely finished and can be returned to - * the caller via cue_parser_get(). - */ - struct song *finished; - - /** - * Set to true after previous.end_time has been updated to the - * start time of the current song. - */ - bool last_updated; - - /** - * Tracks whether cue_parser_finish() has been called. If - * true, then all remaining (partial) results will be - * delivered by cue_parser_get(). - */ - bool end; -}; - -struct cue_parser * -cue_parser_new(void) -{ - struct cue_parser *parser = g_new(struct cue_parser, 1); - parser->state = HEADER; - parser->tag = tag_new(); - parser->filename = NULL; - parser->current = NULL; - parser->previous = NULL; - parser->finished = NULL; - parser->end = false; - return parser; -} - -void -cue_parser_free(struct cue_parser *parser) -{ - tag_free(parser->tag); - g_free(parser->filename); - - if (parser->current != NULL) - song_free(parser->current); - - if (parser->previous != NULL) - song_free(parser->previous); - - if (parser->finished != NULL) - song_free(parser->finished); - - g_free(parser); -} - -static const char * -cue_next_word(char *p, char **pp) -{ - assert(p >= *pp); - assert(!g_ascii_isspace(*p)); - - const char *word = p; - while (*p != 0 && !g_ascii_isspace(*p)) - ++p; - - *p = 0; - *pp = p + 1; - return word; -} - -static const char * -cue_next_quoted(char *p, char **pp) -{ - assert(p >= *pp); - assert(p[-1] == '"'); - - char *end = strchr(p, '"'); - if (end == NULL) { - /* syntax error - ignore it silently */ - *pp = p + strlen(p); - return p; - } - - *end = 0; - *pp = end + 1; - - return p; -} - -static const char * -cue_next_token(char **pp) -{ - char *p = strchug_fast(*pp); - if (*p == 0) - return NULL; - - return cue_next_word(p, pp); -} - -static const char * -cue_next_value(char **pp) -{ - char *p = strchug_fast(*pp); - if (*p == 0) - return NULL; - - if (*p == '"') - return cue_next_quoted(p + 1, pp); - else - return cue_next_word(p, pp); -} - -static void -cue_add_tag(struct tag *tag, enum tag_type type, char *p) -{ - const char *value = cue_next_value(&p); - if (value != NULL) - tag_add_item(tag, type, value); - -} - -static void -cue_parse_rem(char *p, struct tag *tag) -{ - const char *type = cue_next_token(&p); - if (type == NULL) - return; - - enum tag_type type2 = tag_name_parse_i(type); - if (type2 != TAG_NUM_OF_ITEM_TYPES) - cue_add_tag(tag, type2, p); -} - -static struct tag * -cue_current_tag(struct cue_parser *parser) -{ - if (parser->state == HEADER) - return parser->tag; - else if (parser->state == TRACK) - return parser->current->tag; - else - return NULL; -} - -static int -cue_parse_position(const char *p) -{ - char *endptr; - unsigned long minutes = strtoul(p, &endptr, 10); - if (endptr == p || *endptr != ':') - return -1; - - p = endptr + 1; - unsigned long seconds = strtoul(p, &endptr, 10); - if (endptr == p || *endptr != ':') - return -1; - - p = endptr + 1; - unsigned long frames = strtoul(p, &endptr, 10); - if (endptr == p || *endptr != 0) - return -1; - - return minutes * 60000 + seconds * 1000 + frames * 1000 / 75; -} - -/** - * Commit the current song. It will be moved to "previous", so the - * next song may soon edit its end time (using the next song's start - * time). - */ -static void -cue_parser_commit(struct cue_parser *parser) -{ - /* the caller of this library must call cue_parser_get() often - enough */ - assert(parser->finished == NULL); - assert(!parser->end); - - if (parser->current == NULL) - return; - - parser->finished = parser->previous; - parser->previous = parser->current; - parser->current = NULL; -} - -static void -cue_parser_feed2(struct cue_parser *parser, char *p) -{ - assert(parser != NULL); - assert(!parser->end); - assert(p != NULL); - - const char *command = cue_next_token(&p); - if (command == NULL) - return; - - if (strcmp(command, "REM") == 0) { - struct tag *tag = cue_current_tag(parser); - if (tag != NULL) - cue_parse_rem(p, tag); - } else if (strcmp(command, "PERFORMER") == 0) { - /* MPD knows a "performer" tag, but it is not a good - match for this CUE tag; from the Hydrogenaudio - Knowledgebase: "At top-level this will specify the - CD artist, while at track-level it specifies the - track artist." */ - - enum tag_type type = parser->state == TRACK - ? TAG_ARTIST - : TAG_ALBUM_ARTIST; - - struct tag *tag = cue_current_tag(parser); - if (tag != NULL) - cue_add_tag(tag, type, p); - } else if (strcmp(command, "TITLE") == 0) { - if (parser->state == HEADER) - cue_add_tag(parser->tag, TAG_ALBUM, p); - else if (parser->state == TRACK) - cue_add_tag(parser->current->tag, TAG_TITLE, p); - } else if (strcmp(command, "FILE") == 0) { - cue_parser_commit(parser); - - const char *filename = cue_next_value(&p); - if (filename == NULL) - return; - - const char *type = cue_next_token(&p); - if (type == NULL) - return; - - if (strcmp(type, "WAVE") != 0 && - strcmp(type, "MP3") != 0 && - strcmp(type, "AIFF") != 0) { - parser->state = IGNORE_FILE; - return; - } - - parser->state = WAVE; - g_free(parser->filename); - parser->filename = g_strdup(filename); - } else if (parser->state == IGNORE_FILE) { - return; - } else if (strcmp(command, "TRACK") == 0) { - cue_parser_commit(parser); - - const char *nr = cue_next_token(&p); - if (nr == NULL) - return; - - const char *type = cue_next_token(&p); - if (type == NULL) - return; - - if (strcmp(type, "AUDIO") != 0) { - parser->state = IGNORE_TRACK; - return; - } - - parser->state = TRACK; - parser->current = song_remote_new(parser->filename); - assert(parser->current->tag == NULL); - parser->current->tag = tag_dup(parser->tag); - tag_add_item(parser->current->tag, TAG_TRACK, nr); - parser->last_updated = false; - } else if (parser->state == IGNORE_TRACK) { - return; - } else if (parser->state == TRACK && strcmp(command, "INDEX") == 0) { - const char *nr = cue_next_token(&p); - if (nr == NULL) - return; - - const char *position = cue_next_token(&p); - if (position == NULL) - return; - - int position_ms = cue_parse_position(position); - if (position_ms < 0) - return; - - if (!parser->last_updated && parser->previous != NULL && - parser->previous->start_ms < (unsigned)position_ms) { - parser->last_updated = true; - parser->previous->end_ms = position_ms; - parser->previous->tag->time = - (parser->previous->end_ms - parser->previous->start_ms + 500) / 1000; - } - - parser->current->start_ms = position_ms; - } -} - -void -cue_parser_feed(struct cue_parser *parser, const char *line) -{ - assert(parser != NULL); - assert(!parser->end); - assert(line != NULL); - - char *allocated = g_strdup(line); - cue_parser_feed2(parser, allocated); - g_free(allocated); -} - -void -cue_parser_finish(struct cue_parser *parser) -{ - if (parser->end) - /* has already been called, ignore */ - return; - - cue_parser_commit(parser); - parser->end = true; -} - -struct song * -cue_parser_get(struct cue_parser *parser) -{ - assert(parser != NULL); - - if (parser->finished == NULL && parser->end) { - /* cue_parser_finish() has been called already: - deliver all remaining (partial) results */ - assert(parser->current == NULL); - - parser->finished = parser->previous; - parser->previous = NULL; - } - - struct song *song = parser->finished; - parser->finished = NULL; - return song; -} diff --git a/src/cue/cue_parser.h b/src/cue/cue_parser.h deleted file mode 100644 index d8d695739..000000000 --- a/src/cue/cue_parser.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CUE_PARSER_H -#define MPD_CUE_PARSER_H - -#include "check.h" - -#include <stdbool.h> - -struct cue_parser * -cue_parser_new(void); - -void -cue_parser_free(struct cue_parser *parser); - -/** - * Feed a text line from the CUE file into the parser. Call - * cue_parser_get() after this to see if a song has been finished. - */ -void -cue_parser_feed(struct cue_parser *parser, const char *line); - -/** - * Tell the parser that the end of the file has been reached. Call - * cue_parser_get() after this to see if a song has been finished. - * This procedure must be done twice! - */ -void -cue_parser_finish(struct cue_parser *parser); - -/** - * Check if a song was finished by the last cue_parser_feed() or - * cue_parser_finish() call. - * - * @return a song object that must be freed by the caller, or NULL if - * no song was finished at this time - */ -struct song * -cue_parser_get(struct cue_parser *parser); - -#endif diff --git a/src/daemon.c b/src/daemon.c deleted file mode 100644 index 8bca9095a..000000000 --- a/src/daemon.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "daemon.h" - -#include <glib.h> - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -#ifndef WIN32 -#include <signal.h> -#include <pwd.h> -#include <grp.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "daemon" - -#ifndef WIN32 - -/** the Unix user name which MPD runs as */ -static char *user_name; - -/** the Unix user id which MPD runs as */ -static uid_t user_uid = (uid_t)-1; - -/** the Unix group id which MPD runs as */ -static gid_t user_gid = (pid_t)-1; - -/** the absolute path of the pidfile */ -static char *pidfile; - -/* whether "group" conf. option was given */ -static bool had_group = false; - - -void -daemonize_kill(void) -{ - FILE *fp; - int pid, ret; - - if (pidfile == NULL) - MPD_ERROR("no pid_file specified in the config file"); - - fp = fopen(pidfile, "r"); - if (fp == NULL) - MPD_ERROR("unable to open pid file \"%s\": %s", - pidfile, g_strerror(errno)); - - if (fscanf(fp, "%i", &pid) != 1) { - MPD_ERROR("unable to read the pid from file \"%s\"", - pidfile); - } - fclose(fp); - - ret = kill(pid, SIGTERM); - if (ret < 0) - MPD_ERROR("unable to kill process %i: %s", - pid, g_strerror(errno)); - - exit(EXIT_SUCCESS); -} - -void -daemonize_close_stdin(void) -{ - close(STDIN_FILENO); - open("/dev/null", O_RDONLY); -} - -void -daemonize_set_user(void) -{ - if (user_name == NULL) - return; - - /* 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 (!had_group && initgroups(user_name, user_gid) == -1) { - g_warning("cannot init supplementary groups " - "of user \"%s\": %s", - user_name, g_strerror(errno)); - } -#endif - - /* set uid */ - 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)); - } -} - -static void -daemonize_detach(void) -{ - /* flush all file handles before duplicating the buffers */ - - fflush(NULL); - -#ifdef HAVE_DAEMON - - if (daemon(0, 1)) - MPD_ERROR("daemon() failed: %s", g_strerror(errno)); - -#elif defined(HAVE_FORK) - - /* 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) - MPD_ERROR("problems changing to root directory"); - - /* detach from the current session */ - - setsid(); - -#else - MPD_ERROR("no support for daemonizing"); -#endif - - g_debug("daemonized!"); -} - -void -daemonize(bool detach) -{ - FILE *fp = NULL; - - if (pidfile != NULL) { - /* do this before daemon'izing so we can fail gracefully if we can't - * write to the pid file */ - g_debug("opening pid file"); - fp = fopen(pidfile, "w+"); - if (!fp) { - MPD_ERROR("could not create pid file \"%s\": %s", - pidfile, g_strerror(errno)); - } - } - - if (detach) - daemonize_detach(); - - if (pidfile != NULL) { - g_debug("writing pid file"); - fprintf(fp, "%lu\n", (unsigned long)getpid()); - fclose(fp); - } -} - -void -daemonize_init(const char *user, const char *group, const char *_pidfile) -{ - 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); -} - -void -daemonize_finish(void) -{ - if (pidfile != NULL) - unlink(pidfile); - - g_free(user_name); - g_free(pidfile); -} - -#endif diff --git a/src/daemon.h b/src/daemon.h deleted file mode 100644 index c43a74cfd..000000000 --- a/src/daemon.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef DAEMON_H -#define DAEMON_H - -#include "mpd_error.h" - -#include <stdbool.h> - -#ifndef WIN32 -void -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 deleted file mode 100644 index 8c903bb45..000000000 --- a/src/database.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "database.h" -#include "db_error.h" -#include "db_save.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_plugin.h" -#include "db/simple_db_plugin.h" -#include "directory.h" -#include "stats.h" -#include "conf.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "database" - -static struct db *db; -static bool db_is_open; - -bool -db_init(const struct config_param *path, GError **error_r) -{ - assert(db == NULL); - assert(!db_is_open); - - if (path == NULL) - return true; - - struct config_param *param = config_new_param("database", path->line); - config_add_block_param(param, "path", path->value, path->line); - - db = db_plugin_new(&simple_db_plugin, param, error_r); - - config_param_free(param); - - return db != NULL; -} - -void -db_finish(void) -{ - if (db_is_open) - db_plugin_close(db); - - if (db != NULL) - db_plugin_free(db); -} - -struct directory * -db_get_root(void) -{ - assert(db != NULL); - - return simple_db_get_root(db); -} - -struct directory * -db_get_directory(const char *name) -{ - if (db == NULL) - return NULL; - - struct directory *music_root = db_get_root(); - if (name == NULL) - return music_root; - - struct directory *directory = - directory_lookup_directory(music_root, name); - return directory; -} - -struct song * -db_get_song(const char *file) -{ - assert(file != NULL); - - g_debug("get song: %s", file); - - if (db == NULL) - return NULL; - - return db_plugin_get_song(db, file, NULL); -} - -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - if (db == NULL) { - g_set_error_literal(error_r, db_quark(), DB_DISABLED, - "No database"); - return false; - } - - return db_plugin_visit(db, selection, visitor, ctx, error_r); -} - -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri, true); - - return db_visit(&selection, visitor, ctx, error_r); -} - -bool -db_save(GError **error_r) -{ - assert(db != NULL); - assert(db_is_open); - - return simple_db_save(db, error_r); -} - -bool -db_load(GError **error) -{ - assert(db != NULL); - assert(!db_is_open); - - if (!db_plugin_open(db, error)) - return false; - - db_is_open = true; - - stats_update(); - - return true; -} - -time_t -db_get_mtime(void) -{ - assert(db != NULL); - assert(db_is_open); - - return simple_db_get_mtime(db); -} diff --git a/src/database.h b/src/database.h deleted file mode 100644 index f877b74d3..000000000 --- a/src/database.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_H -#define MPD_DATABASE_H - -#include "gcc.h" - -#include <glib.h> - -#include <sys/time.h> -#include <stdbool.h> - -struct config_param; -struct directory; -struct db_selection; -struct db_visitor; - -/** - * Initialize the database library. - * - * @param path the absolute path of the database file - */ -bool -db_init(const struct config_param *path, GError **error_r); - -void -db_finish(void); - -/** - * Returns the root directory object. Returns NULL if there is no - * configured music directory. - */ -G_GNUC_PURE -struct directory * -db_get_root(void); - -/** - * Caller must lock the #db_mutex. - */ -gcc_nonnull(1) -G_GNUC_PURE -struct directory * -db_get_directory(const char *name); - -gcc_nonnull(1) -G_GNUC_PURE -struct song * -db_get_song(const char *file); - -gcc_nonnull(1,2) -bool -db_visit(const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -gcc_nonnull(1,2) -bool -db_walk(const char *uri, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -bool -db_save(GError **error_r); - -bool -db_load(GError **error); - -G_GNUC_PURE -time_t -db_get_mtime(void); - -/** - * Returns true if there is a valid database file on the disk. - */ -G_GNUC_PURE -static inline bool -db_exists(void) -{ - /* mtime is set only if the database file was loaded or saved - successfully */ - return db_get_mtime() > 0; -} - -#endif diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..2b8850f26 --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ProxyDatabasePlugin.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseError.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "gcc.h" +#include "ConfigData.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#undef MPD_DIRECTORY_H +#undef MPD_SONG_H +#include <mpd/client.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxyDatabase : public Database { + std::string host; + unsigned port; + + struct mpd_connection *connection; + Directory *root; + +public: + static Database *Create(const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual Song *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(Song *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + +protected: + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain libmpdclient_domain("libmpdclient"); + +static constexpr struct { + enum tag_type d; + enum mpd_tag_type s; +} tag_table[] = { + { TAG_ARTIST, MPD_TAG_ARTIST }, + { TAG_ALBUM, MPD_TAG_ALBUM }, + { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, + { TAG_TITLE, MPD_TAG_TITLE }, + { TAG_TRACK, MPD_TAG_TRACK }, + { TAG_NAME, MPD_TAG_NAME }, + { TAG_GENRE, MPD_TAG_GENRE }, + { TAG_DATE, MPD_TAG_DATE }, + { TAG_COMPOSER, MPD_TAG_COMPOSER }, + { TAG_PERFORMER, MPD_TAG_PERFORMER }, + { TAG_COMMENT, MPD_TAG_COMMENT }, + { TAG_DISC, MPD_TAG_DISC }, + { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, + { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, + { TAG_MUSICBRAINZ_ALBUMARTISTID, + MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, + { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, + { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } +}; + +gcc_const +static enum mpd_tag_type +Convert(enum tag_type tag_type) +{ + for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + if (i->d == tag_type) + return i->s; + + return MPD_TAG_COUNT; +} + +static bool +CheckError(struct mpd_connection *connection, Error &error) +{ + const auto code = mpd_connection_get_error(connection); + if (code == MPD_ERROR_SUCCESS) + return true; + + error.Set(libmpdclient_domain, (int)code, + mpd_connection_get_error_message(connection)); + mpd_connection_clear_error(connection); + return false; +} + +Database * +ProxyDatabase::Create(const config_param ¶m, Error &error) +{ + ProxyDatabase *db = new ProxyDatabase(); + if (!db->Configure(param, error)) { + delete db; + db = NULL; + } + + return db; +} + +bool +ProxyDatabase::Configure(const config_param ¶m, gcc_unused Error &error) +{ + host = param.GetBlockValue("host", ""); + port = param.GetBlockValue("port", 0u); + + return true; +} + +bool +ProxyDatabase::Open(Error &error) +{ + connection = mpd_connection_new(host.empty() ? NULL : host.c_str(), + port, 0); + if (connection == NULL) { + error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM, "Out of memory"); + return false; + } + + if (!CheckError(connection, error)) { + mpd_connection_free(connection); + return false; + } + + root = Directory::NewRoot(); + + return true; +} + +void +ProxyDatabase::Close() +{ + assert(connection != nullptr); + + root->Free(); + mpd_connection_free(connection); +} + +static Song * +Convert(const struct mpd_song *song); + +Song * +ProxyDatabase::GetSong(const char *uri, Error &error) const +{ + // TODO: implement + // TODO: auto-reconnect + + if (!mpd_send_list_meta(connection, uri)) { + CheckError(connection, error); + return nullptr; + } + + struct mpd_song *song = mpd_recv_song(connection); + Song *song2 = song != nullptr + ? Convert(song) + : nullptr; + mpd_song_free(song); + if (!mpd_response_finish(connection)) { + if (song2 != nullptr) + song2->Free(); + + CheckError(connection, error); + return nullptr; + } + + if (song2 == nullptr) + error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); + + return song2; +} + +void +ProxyDatabase::ReturnSong(Song *song) const +{ + assert(song != nullptr); + assert(song->IsInDatabase()); + assert(song->IsDetached()); + + song->Free(); +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error); + +static bool +Visit(struct mpd_connection *connection, + bool recursive, const struct mpd_directory *directory, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error) +{ + const char *path = mpd_directory_get_path(directory); + + if (visit_directory) { + Directory *d = Directory::NewGeneric(path, &detached_root); + bool success = visit_directory(*d, error); + d->Free(); + if (!success) + return false; + } + + if (recursive && + !Visit(connection, path, recursive, + visit_directory, visit_song, visit_playlist, error)) + return false; + + return true; +} + +static void +Copy(TagBuilder &tag, enum tag_type d_tag, + const struct mpd_song *song, enum mpd_tag_type s_tag) +{ + + for (unsigned i = 0;; ++i) { + const char *value = mpd_song_get_tag(song, s_tag, i); + if (value == NULL) + break; + + tag.AddItem(d_tag, value); + } +} + +static Song * +Convert(const struct mpd_song *song) +{ + Song *s = Song::NewDetached(mpd_song_get_uri(song)); + + s->mtime = mpd_song_get_last_modified(song); + s->start_ms = mpd_song_get_start(song) * 1000; + s->end_ms = mpd_song_get_end(song) * 1000; + + TagBuilder tag; + tag.SetTime(mpd_song_get_duration(song)); + + for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + Copy(tag, i->d, song, i->s); + + s->tag = tag.Commit(); + + return s; +} + +static bool +Visit(const struct mpd_song *song, + VisitSong visit_song, Error &error) +{ + if (!visit_song) + return true; + + Song *s = Convert(song); + bool success = visit_song(*s, error); + s->Free(); + + return success; +} + +static bool +Visit(const struct mpd_playlist *playlist, + VisitPlaylist visit_playlist, Error &error) +{ + if (!visit_playlist) + return true; + + PlaylistInfo p(mpd_playlist_get_path(playlist), + mpd_playlist_get_last_modified(playlist)); + + return visit_playlist(p, detached_root, error); +} + +class ProxyEntity { + struct mpd_entity *entity; + +public: + explicit ProxyEntity(struct mpd_entity *_entity) + :entity(_entity) {} + + ProxyEntity(const ProxyEntity &other) = delete; + + ProxyEntity(ProxyEntity &&other) + :entity(other.entity) { + other.entity = nullptr; + } + + ~ProxyEntity() { + if (entity != nullptr) + mpd_entity_free(entity); + } + + ProxyEntity &operator=(const ProxyEntity &other) = delete; + + operator const struct mpd_entity *() const { + return entity; + } +}; + +static std::list<ProxyEntity> +ReceiveEntities(struct mpd_connection *connection) +{ + std::list<ProxyEntity> entities; + struct mpd_entity *entity; + while ((entity = mpd_recv_entity(connection)) != NULL) + entities.push_back(ProxyEntity(entity)); + + mpd_response_finish(connection); + return entities; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error) +{ + if (!mpd_send_list_meta(connection, uri)) + return CheckError(connection, error); + + std::list<ProxyEntity> entities(ReceiveEntities(connection)); + if (!CheckError(connection, error)) + return false; + + for (const auto &entity : entities) { + switch (mpd_entity_get_type(entity)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + + case MPD_ENTITY_TYPE_DIRECTORY: + if (!Visit(connection, recursive, + mpd_entity_get_directory(entity), + visit_directory, visit_song, visit_playlist, + error)) + return false; + break; + + case MPD_ENTITY_TYPE_SONG: + if (!Visit(mpd_entity_get_song(entity), visit_song, + error)) + return false; + break; + + case MPD_ENTITY_TYPE_PLAYLIST: + if (!Visit(mpd_entity_get_playlist(entity), + visit_playlist, error)) + return false; + break; + } + } + + return CheckError(connection, error); +} + +bool +ProxyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + // TODO: match + // TODO: auto-reconnect + + return ::Visit(connection, selection.uri, selection.recursive, + visit_directory, visit_song, visit_playlist, + error); +} + +bool +ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error) const +{ + enum mpd_tag_type tag_type2 = Convert(tag_type); + if (tag_type2 == MPD_TAG_COUNT) { + error.Set(libmpdclient_domain, "Unsupported tag"); + return false; + } + + if (!mpd_search_db_tags(connection, tag_type2)) + return CheckError(connection, error); + + // TODO: match + (void)selection; + + if (!mpd_search_commit(connection)) + return CheckError(connection, error); + + bool result = true; + + struct mpd_pair *pair; + while (result && + (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) { + result = visit_string(pair->value, error); + mpd_return_pair(connection, pair); + } + + return mpd_response_finish(connection) && + CheckError(connection, error) && + result; +} + +bool +ProxyDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) const +{ + // TODO: match + (void)selection; + + struct mpd_stats *stats2 = + mpd_run_stats(connection); + if (stats2 == nullptr) + return CheckError(connection, error); + + stats.song_count = mpd_stats_get_number_of_songs(stats2); + stats.total_duration = mpd_stats_get_db_play_time(stats2); + stats.artist_count = mpd_stats_get_number_of_artists(stats2); + stats.album_count = mpd_stats_get_number_of_albums(stats2); + mpd_stats_free(stats2); + + return true; +} + +const DatabasePlugin proxy_db_plugin = { + "proxy", + ProxyDatabase::Create, +}; diff --git a/src/db/ProxyDatabasePlugin.hxx b/src/db/ProxyDatabasePlugin.hxx new file mode 100644 index 000000000..8e878baca --- /dev/null +++ b/src/db/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX +#define MPD_PROXY_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin proxy_db_plugin; + +#endif diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx new file mode 100644 index 000000000..2cb820827 --- /dev/null +++ b/src/db/SimpleDatabasePlugin.cxx @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SimpleDatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseHelpers.hxx" +#include "Directory.hxx" +#include "SongFilter.hxx" +#include "DatabaseSave.hxx" +#include "DatabaseLock.hxx" +#include "DatabaseError.hxx" +#include "TextFile.hxx" +#include "ConfigData.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <sys/types.h> +#include <errno.h> + +static constexpr Domain simple_db_domain("simple_db"); + +Database * +SimpleDatabase::Create(const config_param ¶m, Error &error) +{ + SimpleDatabase *db = new SimpleDatabase(); + if (!db->Configure(param, error)) { + delete db; + db = NULL; + } + + return db; +} + +bool +SimpleDatabase::Configure(const config_param ¶m, Error &error) +{ + path = param.GetBlockPath("path", error); + if (path.IsNull()) { + if (!error.IsDefined()) + error.Set(simple_db_domain, + "No \"path\" parameter specified"); + return false; + } + + path_utf8 = path.ToUTF8(); + + return true; +} + +bool +SimpleDatabase::Check(Error &error) const +{ + assert(!path.IsNull()); + + /* Check if the file exists */ + if (!CheckAccess(path, F_OK)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + const Path dirPath = path.GetDirectoryName(); + + /* Check that the parent part of the path is a directory */ + struct stat st; + if (!StatFile(dirPath, st)) { + error.FormatErrno("Couldn't stat parent directory of db file " + "\"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISDIR(st.st_mode)) { + error.Format(simple_db_domain, + "Couldn't create db file \"%s\" because the " + "parent path is not a directory", + path_utf8.c_str()); + return false; + } + + /* Check if we can write to the directory */ + if (!CheckAccess(dirPath, X_OK | W_OK)) { + const int e = errno; + const std::string dirPath_utf8 = dirPath.ToUTF8(); + error.FormatErrno(e, "Can't create db file in \"%s\"", + dirPath_utf8.c_str()); + return false; + } + + return true; + } + + /* Path exists, now check if it's a regular file */ + struct stat st; + if (!StatFile(path, st)) { + error.FormatErrno("Couldn't stat db file \"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISREG(st.st_mode)) { + error.Format(simple_db_domain, + "db file \"%s\" is not a regular file", + path_utf8.c_str()); + return false; + } + + /* And check that we can write to it */ + if (!CheckAccess(path, R_OK | W_OK)) { + error.FormatErrno("Can't open db file \"%s\" for reading/writing", + path_utf8.c_str()); + return false; + } + + return true; +} + +bool +SimpleDatabase::Load(Error &error) +{ + assert(!path.IsNull()); + assert(root != NULL); + + TextFile file(path); + if (file.HasFailed()) { + error.FormatErrno("Failed to open database file \"%s\"", + path_utf8.c_str()); + return false; + } + + if (!db_load_internal(file, root, error)) + return false; + + struct stat st; + if (StatFile(path, st)) + mtime = st.st_mtime; + + return true; +} + +bool +SimpleDatabase::Open(Error &error) +{ + root = Directory::NewRoot(); + mtime = 0; + +#ifndef NDEBUG + borrowed_song_count = 0; +#endif + + if (!Load(error)) { + root->Free(); + + LogError(error); + error.Clear(); + + if (!Check(error)) + return false; + + root = Directory::NewRoot(); + } + + return true; +} + +void +SimpleDatabase::Close() +{ + assert(root != NULL); + assert(borrowed_song_count == 0); + + root->Free(); +} + +Song * +SimpleDatabase::GetSong(const char *uri, Error &error) const +{ + assert(root != NULL); + + db_lock(); + Song *song = root->LookupSong(uri); + db_unlock(); + if (song == NULL) + error.Format(db_domain, DB_NOT_FOUND, + "No such song: %s", uri); +#ifndef NDEBUG + else + ++const_cast<unsigned &>(borrowed_song_count); +#endif + + return song; +} + +void +SimpleDatabase::ReturnSong(gcc_unused Song *song) const +{ + assert(song != nullptr); + +#ifndef NDEBUG + assert(borrowed_song_count > 0); + --const_cast<unsigned &>(borrowed_song_count); +#endif +} + +gcc_pure +const Directory * +SimpleDatabase::LookupDirectory(const char *uri) const +{ + assert(root != NULL); + assert(uri != NULL); + + ScopeDatabaseLock protect; + return root->LookupDirectory(uri); +} + +bool +SimpleDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + ScopeDatabaseLock protect; + + const Directory *directory = root->LookupDirectory(selection.uri); + if (directory == NULL) { + if (visit_song) { + Song *song = root->LookupSong(selection.uri); + if (song != nullptr) + return !selection.Match(*song) || + visit_song(*song, error); + } + + error.Set(db_domain, DB_NOT_FOUND, "No such directory"); + return false; + } + + if (selection.recursive && visit_directory && + !visit_directory(*directory, error)) + return false; + + return directory->Walk(selection.recursive, selection.filter, + visit_directory, visit_song, visit_playlist, + error); +} + +bool +SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error) const +{ + return ::VisitUniqueTags(*this, selection, tag_type, visit_string, + error); +} + +bool +SimpleDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) const +{ + return ::GetStats(*this, selection, stats, error); +} + +bool +SimpleDatabase::Save(Error &error) +{ + db_lock(); + + LogDebug(simple_db_domain, "removing empty directories from DB"); + root->PruneEmpty(); + + LogDebug(simple_db_domain, "sorting DB"); + root->Sort(); + + db_unlock(); + + LogDebug(simple_db_domain, "writing DB"); + + FILE *fp = FOpen(path, FOpenMode::WriteText); + if (!fp) { + error.FormatErrno("unable to write to db file \"%s\"", + path_utf8.c_str()); + return false; + } + + db_save_internal(fp, root); + + if (ferror(fp)) { + error.SetErrno("Failed to write to database file"); + fclose(fp); + return false; + } + + fclose(fp); + + struct stat st; + if (StatFile(path, st)) + mtime = st.st_mtime; + + return true; +} + +const DatabasePlugin simple_db_plugin = { + "simple", + SimpleDatabase::Create, +}; diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx new file mode 100644 index 000000000..5de52cdeb --- /dev/null +++ b/src/db/SimpleDatabasePlugin.hxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SIMPLE_DATABASE_PLUGIN_HXX +#define MPD_SIMPLE_DATABASE_PLUGIN_HXX + +#include "DatabasePlugin.hxx" +#include "fs/Path.hxx" +#include "gcc.h" + +#include <cassert> + +#include <time.h> + +struct Directory; + +class SimpleDatabase : public Database { + Path path; + std::string path_utf8; + + Directory *root; + + time_t mtime; + +#ifndef NDEBUG + unsigned borrowed_song_count; +#endif + + SimpleDatabase() + :path(Path::Null()) {} + +public: + gcc_pure + Directory *GetRoot() { + assert(root != NULL); + + return root; + } + + bool Save(Error &error); + + gcc_pure + time_t GetLastModified() const { + return mtime; + } + + static Database *Create(const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + + virtual Song *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(Song *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + enum tag_type tag_type, + VisitString visit_string, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + +protected: + bool Configure(const config_param ¶m, Error &error); + + gcc_pure + bool Check(Error &error) const; + + bool Load(Error &error); + + gcc_pure + const Directory *LookupDirectory(const char *uri) const; +}; + +extern const DatabasePlugin simple_db_plugin; + +#endif diff --git a/src/db/simple_db_plugin.c b/src/db/simple_db_plugin.c deleted file mode 100644 index 697e8da5f..000000000 --- a/src/db/simple_db_plugin.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "simple_db_plugin.h" -#include "db_internal.h" -#include "db_error.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "db_save.h" -#include "db_lock.h" -#include "conf.h" -#include "directory.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -struct simple_db { - struct db base; - - char *path; - - struct directory *root; - - time_t mtime; -}; - -G_GNUC_CONST -static inline GQuark -simple_db_quark(void) -{ - return g_quark_from_static_string("simple_db"); -} - -G_GNUC_PURE -static const struct directory * -simple_db_lookup_directory(const struct simple_db *db, const char *uri) -{ - assert(db != NULL); - assert(db->root != NULL); - assert(uri != NULL); - - db_lock(); - struct directory *directory = - directory_lookup_directory(db->root, uri); - db_unlock(); - return directory; -} - -static struct db * -simple_db_init(const struct config_param *param, GError **error_r) -{ - struct simple_db *db = g_malloc(sizeof(*db)); - db_base_init(&db->base, &simple_db_plugin); - - GError *error = NULL; - db->path = config_dup_block_path(param, "path", &error); - if (db->path == NULL) { - g_free(db); - if (error != NULL) - g_propagate_error(error_r, error); - else - g_set_error(error_r, simple_db_quark(), 0, - "No \"path\" parameter specified"); - return NULL; - } - - return &db->base; -} - -static void -simple_db_finish(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - g_free(db->path); - g_free(db); -} - -static bool -simple_db_check(struct simple_db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->path != NULL); - - /* Check if the file exists */ - if (access(db->path, F_OK)) { - /* If the file doesn't exist, we can't check if we can write - * it, so we are going to try to get the directory path, and - * see if we can write a file in that */ - char *dirPath = g_path_get_dirname(db->path); - - /* Check that the parent part of the path is a directory */ - struct stat st; - if (stat(dirPath, &st) < 0) { - g_free(dirPath); - g_set_error(error_r, simple_db_quark(), errno, - "Couldn't stat parent directory of db file " - "\"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!S_ISDIR(st.st_mode)) { - g_free(dirPath); - g_set_error(error_r, simple_db_quark(), 0, - "Couldn't create db file \"%s\" because the " - "parent path is not a directory", - db->path); - return false; - } - - /* Check if we can write to the directory */ - if (access(dirPath, X_OK | W_OK)) { - g_set_error(error_r, simple_db_quark(), errno, - "Can't create db file in \"%s\": %s", - dirPath, g_strerror(errno)); - g_free(dirPath); - return false; - } - - g_free(dirPath); - - return true; - } - - /* Path exists, now check if it's a regular file */ - struct stat st; - if (stat(db->path, &st) < 0) { - g_set_error(error_r, simple_db_quark(), errno, - "Couldn't stat db file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, simple_db_quark(), 0, - "db file \"%s\" is not a regular file", - db->path); - return false; - } - - /* And check that we can write to it */ - if (access(db->path, R_OK | W_OK)) { - g_set_error(error_r, simple_db_quark(), errno, - "Can't open db file \"%s\" for reading/writing: %s", - db->path, g_strerror(errno)); - return false; - } - - return true; -} - -static bool -simple_db_load(struct simple_db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->path != NULL); - assert(db->root != NULL); - - FILE *fp = fopen(db->path, "r"); - if (fp == NULL) { - g_set_error(error_r, simple_db_quark(), errno, - "Failed to open database file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - if (!db_load_internal(fp, db->root, error_r)) { - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; - - return true; -} - -static bool -simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - - db->root = directory_new_root(); - db->mtime = 0; - - GError *error = NULL; - if (!simple_db_load(db, &error)) { - directory_free(db->root); - - g_warning("Failed to load database: %s", error->message); - g_error_free(error); - - if (!simple_db_check(db, error_r)) - return false; - - db->root = directory_new_root(); - } - - return true; -} - -static void -simple_db_close(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); - - directory_free(db->root); -} - -static struct song * -simple_db_get_song(struct db *_db, const char *uri, GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db->root != NULL); - - db_lock(); - struct song *song = directory_lookup_song(db->root, uri); - db_unlock(); - if (song == NULL) - g_set_error(error_r, db_quark(), DB_NOT_FOUND, - "No such song: %s", uri); - - return song; -} - -static bool -simple_db_visit(struct db *_db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - const struct simple_db *db = (const struct simple_db *)_db; - const struct directory *directory = - simple_db_lookup_directory(db, selection->uri); - if (directory == NULL) { - struct song *song; - if (visitor->song != NULL && - (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL) - return visitor->song(song, ctx, error_r); - - g_set_error(error_r, db_quark(), DB_NOT_FOUND, - "No such directory"); - return false; - } - - if (selection->recursive && visitor->directory != NULL && - !visitor->directory(directory, ctx, error_r)) - return false; - - db_lock(); - bool ret = directory_walk(directory, selection->recursive, - visitor, ctx, error_r); - db_unlock(); - return ret; -} - -const struct db_plugin simple_db_plugin = { - .name = "simple", - .init = simple_db_init, - .finish = simple_db_finish, - .open = simple_db_open, - .close = simple_db_close, - .get_song = simple_db_get_song, - .visit = simple_db_visit, -}; - -struct directory * -simple_db_get_root(struct db *_db) -{ - struct simple_db *db = (struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->root; -} - -bool -simple_db_save(struct db *_db, GError **error_r) -{ - struct simple_db *db = (struct simple_db *)_db; - struct directory *music_root = db->root; - - db_lock(); - - g_debug("removing empty directories from DB"); - directory_prune_empty(music_root); - - g_debug("sorting DB"); - directory_sort(music_root); - - db_unlock(); - - g_debug("writing DB"); - - FILE *fp = fopen(db->path, "w"); - if (!fp) { - g_set_error(error_r, simple_db_quark(), errno, - "unable to write to db file \"%s\": %s", - db->path, g_strerror(errno)); - return false; - } - - db_save_internal(fp, music_root); - - if (ferror(fp)) { - g_set_error(error_r, simple_db_quark(), errno, - "Failed to write to database file: %s", - g_strerror(errno)); - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (stat(db->path, &st) == 0) - db->mtime = st.st_mtime; - - return true; -} - -time_t -simple_db_get_mtime(const struct db *_db) -{ - const struct simple_db *db = (const struct simple_db *)_db; - - assert(db != NULL); - assert(db->root != NULL); - - return db->mtime; -} diff --git a/src/db/simple_db_plugin.h b/src/db/simple_db_plugin.h deleted file mode 100644 index 511505846..000000000 --- a/src/db/simple_db_plugin.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIMPLE_DB_PLUGIN_H -#define MPD_SIMPLE_DB_PLUGIN_H - -#include <glib.h> -#include <stdbool.h> -#include <time.h> - -extern const struct db_plugin simple_db_plugin; - -struct db; - -G_GNUC_PURE -struct directory * -simple_db_get_root(struct db *db); - -bool -simple_db_save(struct db *db, GError **error_r); - -G_GNUC_PURE -time_t -simple_db_get_mtime(const struct db *db); - -#endif diff --git a/src/dbUtils.c b/src/dbUtils.c deleted file mode 100644 index c212d9f9c..000000000 --- a/src/dbUtils.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "dbUtils.h" -#include "locate.h" -#include "database.h" -#include "db_visitor.h" -#include "playlist.h" -#include "stored_playlist.h" - -#include <glib.h> - -static bool -add_to_queue_song(struct song *song, void *ctx, GError **error_r) -{ - struct player_control *pc = ctx; - - enum playlist_result result = - playlist_append_song(&g_playlist, pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor add_to_queue_visitor = { - .song = add_to_queue_song, -}; - -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r) -{ - return db_walk(uri, &add_to_queue_visitor, pc, error_r); -} - -struct add_data { - const char *path; -}; - -static bool -add_to_spl_song(struct song *song, void *ctx, GError **error_r) -{ - struct add_data *data = ctx; - - if (!spl_append_song(data->path, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor add_to_spl_visitor = { - .song = add_to_spl_song, -}; - -bool -addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, - GError **error_r) -{ - struct add_data data = { - .path = path_utf8, - }; - - return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r); -} - -struct find_add_data { - struct player_control *pc; - const struct locate_item_list *criteria; -}; - -static bool -find_add_song(struct song *song, void *ctx, GError **error_r) -{ - struct find_add_data *data = ctx; - - if (!locate_song_match(song, data->criteria)) - return true; - - enum playlist_result result = - playlist_append_song(&g_playlist, data->pc, - song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor find_add_visitor = { - .song = find_add_song, -}; - -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r) -{ - struct find_add_data data; - data.pc = pc; - data.criteria = criteria; - - return db_walk(name, &find_add_visitor, &data, error_r); -} - -static bool -searchadd_visitor_song(struct song *song, void *_data, GError **error_r) -{ - struct find_add_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - enum playlist_result result = - playlist_append_song(&g_playlist, data->pc, song, NULL); - if (result != PLAYLIST_RESULT_SUCCESS) { - g_set_error(error_r, playlist_quark(), result, - "Playlist error"); - return false; - } - - return true; -} - -static const struct db_visitor searchadd_visitor = { - .song = searchadd_visitor_song, -}; - -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - struct find_add_data data = { - .pc = pc, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchadd_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -struct search_add_playlist_data { - const char *playlist; - const struct locate_item_list *criteria; -}; - -static bool -searchaddpl_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_add_playlist_data *data = _data; - - if (!locate_song_search(song, data->criteria)) - return true; - - if (!spl_append_song(data->playlist, song, error_r)) - return false; - - return true; -} - -static const struct db_visitor searchaddpl_visitor = { - .song = searchaddpl_visitor_song, -}; - -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_add_playlist_data data = { - .playlist = path_utf8, - .criteria = new_list, - }; - - bool success = db_walk(uri, &searchaddpl_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} diff --git a/src/dbUtils.h b/src/dbUtils.h deleted file mode 100644 index 706c807fd..000000000 --- a/src/dbUtils.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_UTILS_H -#define MPD_DB_UTILS_H - -#include "gcc.h" - -#include <glib.h> -#include <stdbool.h> - -struct locate_item_list; -struct player_control; - -gcc_nonnull(1,2) -bool -addAllIn(struct player_control *pc, const char *uri, GError **error_r); - -gcc_nonnull(1,2) -bool -addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findAddIn(struct player_control *pc, const char *name, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_songs(struct player_control *pc, const char *uri, - const struct locate_item_list *criteria, GError **error_r); - -gcc_nonnull(1,2,3) -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const struct locate_item_list *criteria, - GError **error_r); - -#endif diff --git a/src/db_error.h b/src/db_error.h deleted file mode 100644 index d3be582cf..000000000 --- a/src/db_error.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_ERROR_H -#define MPD_DB_ERROR_H - -#include <glib.h> - -enum db_error { - /** - * The database is disabled, i.e. none is configured in this - * MPD instance. - */ - DB_DISABLED, - - DB_NOT_FOUND, -}; - -/** - * Quark for GError.domain; the code is an enum #db_error. - */ -G_GNUC_CONST -static inline GQuark -db_quark(void) -{ - return g_quark_from_static_string("db"); -} - -#endif diff --git a/src/db_internal.h b/src/db_internal.h deleted file mode 100644 index a33351524..000000000 --- a/src/db_internal.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_INTERNAL_H -#define MPD_DB_INTERNAL_H - -#include "db_plugin.h" - -#include <assert.h> - -static inline void -db_base_init(struct db *db, const struct db_plugin *plugin) -{ - assert(plugin != NULL); - - db->plugin = plugin; -} - -#endif diff --git a/src/db_lock.c b/src/db_lock.c deleted file mode 100644 index 53759673d..000000000 --- a/src/db_lock.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_lock.h" -#include "gcc.h" - -#if GCC_CHECK_VERSION(4, 2) -/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -GStaticMutex db_mutex = G_STATIC_MUTEX_INIT; - -#ifndef NDEBUG -GThread *db_mutex_holder; -#endif diff --git a/src/db_lock.h b/src/db_lock.h deleted file mode 100644 index 4640502f3..000000000 --- a/src/db_lock.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Support for locking data structures from the database, for safe - * multi-threading. - */ - -#ifndef MPD_DB_LOCK_H -#define MPD_DB_LOCK_H - -#include "check.h" - -#include <glib.h> -#include <assert.h> -#include <stdbool.h> - -extern GStaticMutex db_mutex; - -#ifndef NDEBUG - -extern GThread *db_mutex_holder; - -/** - * Does the current thread hold the database lock? - */ -G_GNUC_PURE -static inline bool -holding_db_lock(void) -{ - return db_mutex_holder == g_thread_self(); -} - -#endif - -/** - * Obtain the global database lock. This is needed before - * dereferencing a #song or #directory. It is not recursive. - */ -static inline void -db_lock(void) -{ - assert(!holding_db_lock()); - - g_static_mutex_lock(&db_mutex); - - assert(db_mutex_holder == NULL); -#ifndef NDEBUG - db_mutex_holder = g_thread_self(); -#endif -} - -/** - * Release the global database lock. - */ -static inline void -db_unlock(void) -{ - assert(holding_db_lock()); -#ifndef NDEBUG - db_mutex_holder = NULL; -#endif - - g_static_mutex_unlock(&db_mutex); -} - -#endif diff --git a/src/db_plugin.h b/src/db_plugin.h deleted file mode 100644 index 1c7e14ede..000000000 --- a/src/db_plugin.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the db_plugin class. It describes a - * plugin API for databases of song metadata. - */ - -#ifndef MPD_DB_PLUGIN_H -#define MPD_DB_PLUGIN_H - -#include <glib.h> -#include <assert.h> -#include <stdbool.h> - -struct config_param; -struct db_selection; -struct db_visitor; - -struct db { - const struct db_plugin *plugin; -}; - -struct db_plugin { - const char *name; - - /** - * Allocates and configures a database. - */ - struct db *(*init)(const struct config_param *param, GError **error_r); - - /** - * Free instance data. - */ - void (*finish)(struct db *db); - - /** - * Open the database. Read it into memory if applicable. - */ - bool (*open)(struct db *db, GError **error_r); - - /** - * Close the database, free allocated memory. - */ - void (*close)(struct db *db); - - /** - * Look up a song (including tag data) in the database. - * - * @param the URI of the song within the music directory - * (UTF-8) - */ - struct song *(*get_song)(struct db *db, const char *uri, - GError **error_r); - - /** - * Visit the selected entities. - */ - bool (*visit)(struct db *db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r); -}; - -G_GNUC_MALLOC -static inline struct db * -db_plugin_new(const struct db_plugin *plugin, const struct config_param *param, - GError **error_r) -{ - assert(plugin != NULL); - assert(plugin->init != NULL); - assert(plugin->finish != NULL); - assert(plugin->get_song != NULL); - assert(plugin->visit != NULL); - assert(error_r == NULL || *error_r == NULL); - - struct db *db = plugin->init(param, error_r); - assert(db == NULL || db->plugin == plugin); - assert(db != NULL || error_r == NULL || *error_r != NULL); - - return db; -} - -static inline void -db_plugin_free(struct db *db) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(db->plugin->finish != NULL); - - db->plugin->finish(db); -} - -static inline bool -db_plugin_open(struct db *db, GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - - return db->plugin->open != NULL - ? db->plugin->open(db, error_r) - : true; -} - -static inline void -db_plugin_close(struct db *db) -{ - assert(db != NULL); - assert(db->plugin != NULL); - - if (db->plugin->close != NULL) - db->plugin->close(db); -} - -static inline struct song * -db_plugin_get_song(struct db *db, const char *uri, GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(db->plugin->get_song != NULL); - assert(uri != NULL); - - return db->plugin->get_song(db, uri, error_r); -} - -static inline bool -db_plugin_visit(struct db *db, const struct db_selection *selection, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - assert(db != NULL); - assert(db->plugin != NULL); - assert(selection != NULL); - assert(visitor != NULL); - assert(error_r == NULL || *error_r == NULL); - - return db->plugin->visit(db, selection, visitor, ctx, error_r); -} - -#endif diff --git a/src/db_print.c b/src/db_print.c deleted file mode 100644 index 4d7e3f5ef..000000000 --- a/src/db_print.c +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_print.h" -#include "db_selection.h" -#include "db_visitor.h" -#include "locate.h" -#include "directory.h" -#include "database.h" -#include "client.h" -#include "song.h" -#include "song_print.h" -#include "playlist_vector.h" -#include "tag.h" -#include "strset.h" - -#include <glib.h> - -typedef struct _ListCommandItem { - int8_t tagType; - const struct locate_item_list *criteria; -} ListCommandItem; - -typedef struct _SearchStats { - const struct locate_item_list *criteria; - int numberOfSongs; - unsigned long playTime; -} SearchStats; - -static bool -print_visitor_directory(const struct directory *directory, void *data, - G_GNUC_UNUSED GError **error_r) -{ - struct client *client = data; - - if (!directory_is_root(directory)) - client_printf(client, "directory: %s\n", directory_get_path(directory)); - - return true; -} - -static void -print_playlist_in_directory(struct client *client, - const struct directory *directory, - const char *name_utf8) -{ - if (directory_is_root(directory)) - client_printf(client, "playlist: %s\n", name_utf8); - else - client_printf(client, "playlist: %s/%s\n", - directory_get_path(directory), name_utf8); -} - -static bool -print_visitor_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_uri(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_song_info(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - assert(song != NULL); - assert(song->parent != NULL); - - struct client *client = data; - song_print_info(client, song); - - if (song->tag != NULL && song->tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, song->parent, - song->uri); - - return true; -} - -static bool -print_visitor_playlist(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - G_GNUC_UNUSED GError **error_r) -{ - struct client *client = ctx; - print_playlist_in_directory(client, directory, playlist->name); - return true; -} - -static bool -print_visitor_playlist_info(const struct playlist_metadata *playlist, - const struct directory *directory, - void *ctx, G_GNUC_UNUSED GError **error_r) -{ - struct client *client = ctx; - print_playlist_in_directory(client, directory, playlist->name); - -#ifndef G_OS_WIN32 - struct tm tm; -#endif - char timestamp[32]; - time_t t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), -#ifdef G_OS_WIN32 - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&t) -#else - "%FT%TZ", - gmtime_r(&t, &tm) -#endif - ); - client_printf(client, "Last-Modified: %s\n", timestamp); - - return true; -} - -static const struct db_visitor print_visitor = { - .directory = print_visitor_directory, - .song = print_visitor_song, - .playlist = print_visitor_playlist, -}; - -static const struct db_visitor print_info_visitor = { - .directory = print_visitor_directory, - .song = print_visitor_song_info, - .playlist = print_visitor_playlist_info, -}; - -bool -db_selection_print(struct client *client, const struct db_selection *selection, - bool full, GError **error_r) -{ - return db_visit(selection, full ? &print_info_visitor : &print_visitor, - client, error_r); -} - -struct search_data { - struct client *client; - const struct locate_item_list *criteria; -}; - -static bool -search_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_data *data = _data; - - if (locate_song_search(song, data->criteria)) - song_print_info(data->client, song); - - return true; -} - -static const struct db_visitor search_visitor = { - .song = search_visitor_song, -}; - -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_data data; - - data.client = client; - data.criteria = new_list; - - bool success = db_walk(name, &search_visitor, &data, error_r); - - locate_item_list_free(new_list); - - return success; -} - -static bool -find_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct search_data *data = _data; - - if (locate_song_match(song, data->criteria)) - song_print_info(data->client, song); - - return true; -} - -static const struct db_visitor find_visitor = { - .song = find_visitor_song, -}; - -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - struct search_data data; - - data.client = client; - data.criteria = criteria; - - return db_walk(name, &find_visitor, &data, error_r); -} - -static void printSearchStats(struct client *client, SearchStats *stats) -{ - client_printf(client, "songs: %i\n", stats->numberOfSongs); - client_printf(client, "playtime: %li\n", stats->playTime); -} - -static bool -stats_visitor_song(struct song *song, void *data, - G_GNUC_UNUSED GError **error_r) -{ - SearchStats *stats = data; - - if (locate_song_match(song, stats->criteria)) { - stats->numberOfSongs++; - stats->playTime += song_get_duration(song); - } - - return true; -} - -static const struct db_visitor stats_visitor = { - .song = stats_visitor_song, -}; - -bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r) -{ - SearchStats stats; - - stats.criteria = criteria; - stats.numberOfSongs = 0; - stats.playTime = 0; - - if (!db_walk(name, &stats_visitor, &stats, error_r)) - return false; - - printSearchStats(client, &stats); - return true; -} - -bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri_utf8, true); - return db_selection_print(client, &selection, false, error_r); -} - -bool -printInfoForAllIn(struct client *client, const char *uri_utf8, - GError **error_r) -{ - struct db_selection selection; - db_selection_init(&selection, uri_utf8, true); - return db_selection_print(client, &selection, true, error_r); -} - -static ListCommandItem * -newListCommandItem(int tagType, const struct locate_item_list *criteria) -{ - ListCommandItem *item = g_new(ListCommandItem, 1); - - item->tagType = tagType; - item->criteria = criteria; - - return item; -} - -static void freeListCommandItem(ListCommandItem * item) -{ - g_free(item); -} - -static void -visitTag(struct client *client, struct strset *set, - struct song *song, enum tag_type tagType) -{ - struct tag *tag = song->tag; - bool found = false; - - if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_uri(client, song); - return; - } - - if (!tag) - return; - - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == tagType) { - strset_add(set, tag->items[i]->value); - found = true; - } - } - - if (!found) - strset_add(set, ""); -} - -struct list_tags_data { - struct client *client; - ListCommandItem *item; - struct strset *set; -}; - -static bool -unique_tags_visitor_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct list_tags_data *data = _data; - ListCommandItem *item = data->item; - - if (locate_song_match(song, item->criteria)) - visitTag(data->client, data->set, song, item->tagType); - - return true; -} - -static const struct db_visitor unique_tags_visitor = { - .song = unique_tags_visitor_song, -}; - -bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, - GError **error_r) -{ - ListCommandItem *item = newListCommandItem(type, criteria); - struct list_tags_data data = { - .client = client, - .item = item, - }; - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - data.set = strset_new(); - } - - if (!db_walk("", &unique_tags_visitor, &data, error_r)) { - freeListCommandItem(item); - return false; - } - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - const char *value; - - strset_rewind(data.set); - - while ((value = strset_next(data.set)) != NULL) - client_printf(client, "%s: %s\n", - tag_item_names[type], - value); - - strset_free(data.set); - } - - freeListCommandItem(item); - - return true; -} diff --git a/src/db_print.h b/src/db_print.h deleted file mode 100644 index 1b957da18..000000000 --- a/src/db_print.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_PRINT_H -#define MPD_DB_PRINT_H - -#include "gcc.h" - -#include <glib.h> -#include <stdbool.h> - -struct client; -struct locate_item_list; -struct db_selection; -struct db_visitor; - -gcc_nonnull(1,2) -bool -db_selection_print(struct client *client, const struct db_selection *selection, - bool full, GError **error_r); - -gcc_nonnull(1,2) -bool -printAllIn(struct client *client, const char *uri_utf8, GError **error_r); - -gcc_nonnull(1,2) -bool -printInfoForAllIn(struct client *client, const char *uri_utf8, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -findSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,2,3) -bool -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria, - GError **error_r); - -gcc_nonnull(1,3) -bool -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria, - GError **error_r); - -#endif diff --git a/src/db_save.c b/src/db_save.c deleted file mode 100644 index 4af9d58b8..000000000 --- a/src/db_save.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "db_save.h" -#include "db_lock.h" -#include "directory.h" -#include "directory_save.h" -#include "song.h" -#include "path.h" -#include "text_file.h" -#include "tag.h" -#include "tag_internal.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "database" - -#define DIRECTORY_INFO_BEGIN "info_begin" -#define DIRECTORY_INFO_END "info_end" -#define DB_FORMAT_PREFIX "format: " -#define DIRECTORY_MPD_VERSION "mpd_version: " -#define DIRECTORY_FS_CHARSET "fs_charset: " -#define DB_TAG_PREFIX "tag: " - -enum { - DB_FORMAT = 1, -}; - -G_GNUC_CONST -static GQuark -db_quark(void) -{ - return g_quark_from_static_string("database"); -} - -void -db_save_internal(FILE *fp, const struct directory *music_root) -{ - assert(music_root != NULL); - - fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); - fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); - fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); - fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset()); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (!ignore_tag_items[i]) - fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); - - fprintf(fp, "%s\n", DIRECTORY_INFO_END); - - directory_save(fp, music_root); -} - -bool -db_load_internal(FILE *fp, struct directory *music_root, GError **error) -{ - GString *buffer = g_string_sized_new(1024); - char *line; - int format = 0; - bool found_charset = false, found_version = false; - bool success; - bool tags[TAG_NUM_OF_ITEM_TYPES]; - - assert(music_root != NULL); - - /* get initial info */ - line = read_text_line(fp, buffer); - if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { - g_set_error(error, db_quark(), 0, "Database corrupted"); - g_string_free(buffer, true); - return false; - } - - memset(tags, false, sizeof(tags)); - - while ((line = read_text_line(fp, buffer)) != NULL && - strcmp(line, DIRECTORY_INFO_END) != 0) { - if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { - format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); - } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { - if (found_version) { - g_set_error(error, db_quark(), 0, - "Duplicate version line"); - g_string_free(buffer, true); - return false; - } - - found_version = true; - } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { - const char *new_charset, *old_charset; - - if (found_charset) { - g_set_error(error, db_quark(), 0, - "Duplicate charset line"); - g_string_free(buffer, true); - return false; - } - - found_charset = true; - - new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; - old_charset = path_get_fs_charset(); - if (old_charset != NULL - && strcmp(new_charset, old_charset)) { - g_set_error(error, db_quark(), 0, - "Existing database has charset " - "\"%s\" instead of \"%s\"; " - "discarding database file", - new_charset, old_charset); - g_string_free(buffer, true); - return false; - } - } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { - const char *name = line + sizeof(DB_TAG_PREFIX) - 1; - enum tag_type tag = tag_name_parse(name); - if (tag == TAG_NUM_OF_ITEM_TYPES) { - g_set_error(error, db_quark(), 0, - "Unrecognized tag '%s', " - "discarding database file", - name); - return false; - } - - tags[tag] = true; - } else { - g_set_error(error, db_quark(), 0, - "Malformed line: %s", line); - g_string_free(buffer, true); - return false; - } - } - - if (format != DB_FORMAT) { - g_set_error(error, db_quark(), 0, - "Database format mismatch, " - "discarding database file"); - return false; - } - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - if (!ignore_tag_items[i] && !tags[i]) { - g_set_error(error, db_quark(), 0, - "Tag list mismatch, " - "discarding database file"); - return false; - } - } - - g_debug("reading DB"); - - db_lock(); - success = directory_load(fp, music_root, buffer, error); - db_unlock(); - g_string_free(buffer, true); - - return success; -} diff --git a/src/db_save.h b/src/db_save.h deleted file mode 100644 index e760ec881..000000000 --- a/src/db_save.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_SAVE_H -#define MPD_DB_SAVE_H - -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> - -struct directory; - -void -db_save_internal(FILE *file, const struct directory *root); - -bool -db_load_internal(FILE *file, struct directory *root, GError **error); - -#endif diff --git a/src/db_selection.h b/src/db_selection.h deleted file mode 100644 index 2cebb4907..000000000 --- a/src/db_selection.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_SELECTION_H -#define MPD_DB_SELECTION_H - -#include "gcc.h" - -#include <assert.h> - -struct directory; -struct song; - -struct db_selection { - /** - * The base URI of the search (UTF-8). Must not begin or end - * with a slash. NULL or an empty string searches the whole - * database. - */ - const char *uri; - - /** - * Recursively search all sub directories? - */ - bool recursive; -}; - -gcc_nonnull(1,2) -static inline void -db_selection_init(struct db_selection *selection, - const char *uri, bool recursive) -{ - assert(selection != NULL); - assert(uri != NULL); - - selection->uri = uri; - selection->recursive = recursive; -} - -#endif diff --git a/src/db_visitor.h b/src/db_visitor.h deleted file mode 100644 index 6b90c1868..000000000 --- a/src/db_visitor.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_VISITOR_H -#define MPD_DB_VISITOR_H - -struct directory; -struct song; -struct playlist_metadata; - -struct db_visitor { - /** - * Visit a directory. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*directory)(const struct directory *directory, void *ctx, - GError **error_r); - - /** - * Visit a song. Optional method. - * - * @return true to continue the operation, false on error (set error_r) - */ - bool (*song)(struct song *song, void *ctx, GError **error_r); - - /** - * Visit a playlist. Optional method. - * - * @param directory the directory the playlist resides in - * @return true to continue the operation, false on error (set error_r) - */ - bool (*playlist)(const struct playlist_metadata *playlist, - const struct directory *directory, void *ctx, - GError **error_r); -}; - -#endif diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..3ec48cb23 --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.cxx @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AdPlugDecoderPlugin.h" +#include "tag/TagHandler.hxx" +#include "DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <glib.h> + +#include <assert.h> + +static unsigned sample_rate; + +static bool +adplug_init(const config_param ¶m) +{ + Error error; + + sample_rate = param.GetBlockValue("sample_rate", 48000u); + if (!audio_check_sample_rate(sample_rate, error)) { + LogError(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(struct decoder *decoder, const char *path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return; + + const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, false, + player->songlength() / 1000.); + + int16_t buffer[2048]; + const unsigned frames_per_buffer = G_N_ELEMENTS(buffer) / 2; + DecoderCommand cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, NULL, + buffer, sizeof(buffer), + 0); + } while (cmd == DecoderCommand::NONE); + + delete player; +} + +static void +adplug_scan_tag(enum tag_type type, const std::string &value, + const struct tag_handler *handler, void *handler_ctx) +{ + if (!value.empty()) + tag_handler_invoke_tag(handler, handler_ctx, + type, value.c_str()); +} + +static bool +adplug_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + player->songlength() / 1000); + + if (handler->tag != nullptr) { + adplug_scan_tag(TAG_TITLE, player->gettitle(), + handler, handler_ctx); + adplug_scan_tag(TAG_ARTIST, player->getauthor(), + handler, handler_ctx); + adplug_scan_tag(TAG_COMMENT, player->getdesc(), + handler, handler_ctx); + } + + delete player; + return true; +} + +static const char *const adplug_suffixes[] = { + "amd", + "d00", + "hsc", + "laa", + "rad", + "raw", + "sa2", + nullptr +}; + +const struct decoder_plugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..9fdf438aa --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct decoder_plugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx new file mode 100644 index 000000000..c7b1b1016 --- /dev/null +++ b/src/decoder/AudiofileDecoderPlugin.cxx @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AudiofileDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <audiofile.h> +#include <af_vfs.h> + +#include <assert.h> + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +static constexpr Domain audiofile_domain("audiofile"); + +static int audiofile_get_duration(const char *file) +{ + int total_time; + AFfilehandle af_fp = afOpenFile(file, "r", nullptr); + if (af_fp == AF_NULL_FILEHANDLE) { + return -1; + } + total_time = (int) + ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) + / afGetRate(af_fp, AF_DEFAULT_TRACK)); + afCloseFile(af_fp); + return total_time; +} + +static ssize_t +audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + + Error error; + size_t nbytes = is->LockRead(data, length, error); + if (nbytes == 0 && error.IsDefined()) { + LogError(error); + return -1; + } + + return nbytes; +} + +static AFfileoffset +audiofile_file_length(AFvirtualfile *vfile) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + return is->GetSize(); +} + +static AFfileoffset +audiofile_file_tell(AFvirtualfile *vfile) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + return is->GetOffset(); +} + +static void +audiofile_file_destroy(AFvirtualfile *vfile) +{ + assert(vfile->closure != nullptr); + + vfile->closure = nullptr; +} + +static AFfileoffset +audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + int whence = (is_relative ? SEEK_CUR : SEEK_SET); + + Error error; + if (is->LockSeek(offset, whence, error)) { + return is->GetOffset(); + } else { + return -1; + } +} + +static AFvirtualfile * +setup_virtual_fops(struct input_stream *stream) +{ + AFvirtualfile *vf = new AFvirtualfile(); + vf->closure = stream; + vf->write = nullptr; + vf->read = audiofile_file_read; + vf->length = audiofile_file_length; + vf->destroy = audiofile_file_destroy; + vf->seek = audiofile_file_seek; + vf->tell = audiofile_file_tell; + return vf; +} + +static SampleFormat +audiofile_bits_to_sample_format(int bits) +{ + switch (bits) { + case 8: + return SampleFormat::S8; + + case 16: + return SampleFormat::S16; + + case 24: + return SampleFormat::S24_P32; + + case 32: + return SampleFormat::S32; + } + + return SampleFormat::UNDEFINED; +} + +static SampleFormat +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))) { + FormatDebug(audiofile_domain, + "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) +{ + AFvirtualfile *vf; + int fs, frame_count; + AFfilehandle af_fp; + AudioFormat audio_format; + float total_time; + uint16_t bit_rate; + int ret; + char chunk[CHUNK_SIZE]; + + if (!is->IsSeekable()) { + LogWarning(audiofile_domain, "not seekable"); + return; + } + + vf = setup_virtual_fops(is); + + af_fp = afOpenVirtualFile(vf, "r", nullptr); + if (af_fp == AF_NULL_FILEHANDLE) { + LogWarning(audiofile_domain, "failed to input stream"); + return; + } + + Error error; + 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)) { + LogError(error); + afCloseFile(af_fp); + return; + } + + frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + + total_time = ((float)frame_count / (float)audio_format.sample_rate); + + bit_rate = (uint16_t)(is->GetSize() * 8.0 / total_time / 1000.0 + 0.5); + + fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); + + decoder_initialized(decoder, audio_format, true, total_time); + + DecoderCommand cmd; + do { + ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, + CHUNK_SIZE / fs); + if (ret <= 0) + break; + + cmd = decoder_data(decoder, nullptr, + chunk, ret * fs, + bit_rate); + + if (cmd == DecoderCommand::SEEK) { + AFframecount frame = decoder_seek_where(decoder) * + audio_format.sample_rate; + afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); + + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + afCloseFile(af_fp); +} + +static bool +audiofile_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) +{ + int total_time = audiofile_get_duration(file); + + if (total_time < 0) { + FormatWarning(audiofile_domain, + "Failed to get total song time from: %s", + file); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; +} + +static const char *const audiofile_suffixes[] = { + "wav", "au", "aiff", "aif", nullptr +}; + +static const char *const audiofile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + nullptr +}; + +const struct decoder_plugin audiofile_decoder_plugin = { + "audiofile", + nullptr, + nullptr, + audiofile_stream_decode, + nullptr, + audiofile_scan_file, + nullptr, + nullptr, + audiofile_suffixes, + audiofile_mime_types, +}; diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/AudiofileDecoderPlugin.hxx new file mode 100644 index 000000000..59c09c006 --- /dev/null +++ b/src/decoder/AudiofileDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_AUDIOFILE_HXX +#define MPD_DECODER_AUDIOFILE_HXX + +extern const struct decoder_plugin audiofile_decoder_plugin; + +#endif diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx new file mode 100644 index 000000000..7135c9903 --- /dev/null +++ b/src/decoder/DsdLib.cxx @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 file contains functions used by the DSF and DSDIFF decoders. + * + */ + +#include "config.h" +#include "DsdLib.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "util/bit_reverse.h" +#include "tag/TagHandler.hxx" +#include "tag/TagId3.hxx" +#include "util/Error.hxx" + +#include <unistd.h> +#include <string.h> +#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +bool +dsdlib_id_equals(const struct dsdlib_id *id, const char *s) +{ + assert(id != nullptr); + assert(s != nullptr); + assert(strlen(s) == sizeof(id->value)); + + return memcmp(id->value, s, sizeof(id->value)) == 0; +} + +bool +dsdlib_read(struct decoder *decoder, struct input_stream *is, + void *data, size_t length) +{ + size_t nbytes = decoder_read(decoder, is, data, length); + return nbytes == length; +} + +/** + * Skip the #input_stream to the specified offset. + */ +bool +dsdlib_skip_to(struct decoder *decoder, struct input_stream *is, + goffset offset) +{ + if (is->IsSeekable()) + return is->Seek(offset, SEEK_SET, IgnoreError()); + + if (is->GetOffset() > offset) + return false; + + char buffer[8192]; + while (is->GetOffset() < offset) { + size_t length = sizeof(buffer); + if (offset - is->GetOffset() < (goffset)length) + length = offset - is->GetOffset(); + + size_t nbytes = decoder_read(decoder, is, buffer, length); + if (nbytes == 0) + return false; + } + + assert(is->GetOffset() == offset); + return true; +} + +/** + * Skip some bytes from the #input_stream. + */ +bool +dsdlib_skip(struct decoder *decoder, struct input_stream *is, + goffset delta) +{ + assert(delta >= 0); + + if (delta == 0) + return true; + + if (is->IsSeekable()) + return is->Seek(delta, SEEK_CUR, IgnoreError()); + + char buffer[8192]; + while (delta > 0) { + size_t length = sizeof(buffer); + if ((goffset)length > delta) + length = delta; + + size_t nbytes = decoder_read(decoder, is, buffer, length); + if (nbytes == 0) + return false; + + delta -= nbytes; + } + + return true; +} + +/** + * Add tags from ID3 tag. All tags commonly found in the ID3 tags of + * DSF and DSDIFF files are imported + */ + +#ifdef HAVE_ID3TAG +void +dsdlib_tag_id3(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx, goffset tagoffset) +{ + assert(tagoffset >= 0); + + if (tagoffset == 0) + return; + + if (!dsdlib_skip_to(nullptr, is, tagoffset)) + return; + + struct id3_tag *id3_tag = nullptr; + id3_length_t count; + + /* Prevent broken files causing problems */ + const goffset size = is->GetSize(); + const goffset offset = is->GetOffset(); + if (offset >= size) + return; + + count = size - offset; + + /* Check and limit id3 tag size to prevent a stack overflow */ + if (count == 0 || count > 4096) + return; + + id3_byte_t dsdid3[count]; + id3_byte_t *dsdid3data; + dsdid3data = dsdid3; + + if (!dsdlib_read(nullptr, is, dsdid3data, count)) + return; + + id3_tag = id3_tag_parse(dsdid3data, count); + if (id3_tag == nullptr) + return; + + scan_id3_tag(id3_tag, handler, handler_ctx); + + id3_tag_delete(id3_tag); + + return; +} +#endif diff --git a/src/decoder/DsdLib.hxx b/src/decoder/DsdLib.hxx new file mode 100644 index 000000000..2a8e15190 --- /dev/null +++ b/src/decoder/DsdLib.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DSDLIB_HXX +#define MPD_DECODER_DSDLIB_HXX + +#include <stdlib.h> + +#include <glib.h> + +struct dsdlib_id { + char value[4]; +}; + +bool +dsdlib_id_equals(const struct dsdlib_id *id, const char *s); + +bool +dsdlib_read(struct decoder *decoder, struct input_stream *is, + void *data, size_t length); + +bool +dsdlib_skip_to(struct decoder *decoder, struct input_stream *is, + goffset offset); + +bool +dsdlib_skip(struct decoder *decoder, struct input_stream *is, + goffset delta); + +void +dsdlib_tag_id3(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx, goffset tagoffset); + +#endif diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx new file mode 100644 index 000000000..43002768a --- /dev/null +++ b/src/decoder/DsdiffDecoderPlugin.cxx @@ -0,0 +1,527 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* \file + * + * This plugin decodes DSDIFF data (SACD) embedded in DFF files. + * The DFF code was modeled after the specification found here: + * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf + * + * All functions common to both DSD decoders have been moved to dsdlib + */ + +#include "config.h" +#include "DsdiffDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "tag/TagHandler.hxx" +#include "DsdLib.hxx" +#include "Log.hxx" + +#include <unistd.h> +#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ + +struct DsdiffHeader { + struct dsdlib_id id; + uint32_t size_high, size_low; + struct dsdlib_id format; +}; + +struct DsdiffChunkHeader { + struct dsdlib_id id; + uint32_t size_high, size_low; + + /** + * Read the "size" attribute from the specified header, converting it + * to the host byte order if needed. + */ + gcc_const + uint64_t GetSize() const { + return (((uint64_t)GUINT32_FROM_BE(size_high)) << 32) | + ((uint64_t)GUINT32_FROM_BE(size_low)); + } +}; + +/** struct for DSDIFF native Artist and Title tags */ +struct dsdiff_native_tag { + uint32_t size; +}; + +struct DsdiffMetaData { + unsigned sample_rate, channels; + bool bitreverse; + uint64_t chunk_size; +#ifdef HAVE_ID3TAG + goffset id3_offset; + uint64_t id3_size; +#endif + /** offset for artist tag */ + goffset diar_offset; + /** offset for title tag */ + goffset diti_offset; +}; + +static bool lsbitfirst; + +static bool +dsdiff_init(const config_param ¶m) +{ + lsbitfirst = param.GetBlockValue("lsbitfirst", false); + return true; +} + +static bool +dsdiff_read_id(struct decoder *decoder, struct input_stream *is, + struct dsdlib_id *id) +{ + return dsdlib_read(decoder, is, id, sizeof(*id)); +} + +static bool +dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is, + DsdiffChunkHeader *header) +{ + return dsdlib_read(decoder, is, header, sizeof(*header)); +} + +static bool +dsdiff_read_payload(struct decoder *decoder, struct input_stream *is, + const DsdiffChunkHeader *header, + void *data, size_t length) +{ + uint64_t size = header->GetSize(); + if (size != (uint64_t)length) + return false; + + size_t nbytes = decoder_read(decoder, is, data, length); + return nbytes == length; +} + +/** + * Read and parse a "SND" chunk inside "PROP". + */ +static bool +dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, + DsdiffMetaData *metadata, + goffset end_offset) +{ + DsdiffChunkHeader header; + while ((goffset)(is->GetOffset() + sizeof(header)) <= end_offset) { + if (!dsdiff_read_chunk_header(decoder, is, &header)) + return false; + + goffset chunk_end_offset = is->GetOffset() + + header.GetSize(); + if (chunk_end_offset > end_offset) + return false; + + if (dsdlib_id_equals(&header.id, "FS ")) { + uint32_t sample_rate; + if (!dsdiff_read_payload(decoder, is, &header, + &sample_rate, + sizeof(sample_rate))) + return false; + + metadata->sample_rate = GUINT32_FROM_BE(sample_rate); + } else if (dsdlib_id_equals(&header.id, "CHNL")) { + uint16_t channels; + if (header.GetSize() < sizeof(channels) || + !dsdlib_read(decoder, is, + &channels, sizeof(channels)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + metadata->channels = GUINT16_FROM_BE(channels); + } else if (dsdlib_id_equals(&header.id, "CMPR")) { + struct dsdlib_id type; + if (header.GetSize() < sizeof(type) || + !dsdlib_read(decoder, is, + &type, sizeof(type)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + if (!dsdlib_id_equals(&type, "DSD ")) + /* only uncompressed DSD audio data + is implemented */ + return false; + } else { + /* ignore unknown chunk */ + + if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } + + return is->GetOffset() == end_offset; +} + +/** + * Read and parse a "PROP" chunk. + */ +static bool +dsdiff_read_prop(struct decoder *decoder, struct input_stream *is, + DsdiffMetaData *metadata, + const DsdiffChunkHeader *prop_header) +{ + uint64_t prop_size = prop_header->GetSize(); + goffset end_offset = is->GetOffset() + prop_size; + + struct dsdlib_id prop_id; + if (prop_size < sizeof(prop_id) || + !dsdiff_read_id(decoder, is, &prop_id)) + return false; + + if (dsdlib_id_equals(&prop_id, "SND ")) + return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); + else + /* ignore unknown PROP chunk */ + return dsdlib_skip_to(decoder, is, end_offset); +} + +static void +dsdiff_handle_native_tag(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx, goffset tagoffset, + enum tag_type type) +{ + if (!dsdlib_skip_to(nullptr, is, tagoffset)) + return; + + struct dsdiff_native_tag metatag; + + if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag))) + return; + + uint32_t length = GUINT32_FROM_BE(metatag.size); + + /* Check and limit size of the tag to prevent a stack overflow */ + if (length == 0 || length > 60) + return; + + char string[length]; + char *label; + label = string; + + if (!dsdlib_read(nullptr, is, label, (size_t)length)) + return; + + string[length] = '\0'; + tag_handler_invoke_tag(handler, handler_ctx, type, label); + return; +} + +/** + * Read and parse additional metadata chunks for tagging purposes. By default + * dsdiff files only support equivalents for artist and title but some of the + * extract tools add an id3 tag to provide more tags. If such id3 is found + * this will be used for tagging otherwise the native tags (if any) will be + * used + */ + +static bool +dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header, + const struct tag_handler *handler, + void *handler_ctx) +{ + + /* skip from DSD data to next chunk header */ + if (!dsdlib_skip(decoder, is, metadata->chunk_size)) + return false; + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + +#ifdef HAVE_ID3TAG + metadata->id3_size = 0; +#endif + + /* Now process all the remaining chunk headers in the stream + and record their position and size */ + + const goffset size = is->GetSize(); + while (is->GetOffset() < size) { + uint64_t chunk_size = chunk_header->GetSize(); + + /* DIIN chunk, is directly followed by other chunks */ + if (dsdlib_id_equals(&chunk_header->id, "DIIN")) + chunk_size = 0; + + /* DIAR chunk - DSDIFF native tag for Artist */ + if (dsdlib_id_equals(&chunk_header->id, "DIAR")) { + chunk_size = chunk_header->GetSize(); + metadata->diar_offset = is->GetOffset(); + } + + /* DITI chunk - DSDIFF native tag for Title */ + if (dsdlib_id_equals(&chunk_header->id, "DITI")) { + chunk_size = chunk_header->GetSize(); + metadata->diti_offset = is->GetOffset(); + } +#ifdef HAVE_ID3TAG + /* 'ID3 ' chunk, offspec. Used by sacdextract */ + if (dsdlib_id_equals(&chunk_header->id, "ID3 ")) { + chunk_size = chunk_header->GetSize(); + metadata->id3_offset = is->GetOffset(); + metadata->id3_size = chunk_size; + } +#endif + if (chunk_size != 0) { + if (!dsdlib_skip(decoder, is, chunk_size)) + break; + } + + if (is->GetOffset() < size) { + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + } + chunk_size = 0; + } + /* done processing chunk headers, process tags if any */ + +#ifdef HAVE_ID3TAG + if (metadata->id3_offset != 0) + { + /* a ID3 tag has preference over the other tags, do not process + other tags if we have one */ + dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset); + return true; + } +#endif + + if (metadata->diar_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + metadata->diar_offset, TAG_ARTIST); + + if (metadata->diti_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + metadata->diti_offset, TAG_TITLE); + return true; +} + +/** + * Read and parse all metadata chunks at the beginning. Stop when the + * first "DSD" chunk is seen, and return its header in the + * "chunk_header" parameter. + */ +static bool +dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header) +{ + DsdiffHeader header; + if (!dsdlib_read(decoder, is, &header, sizeof(header)) || + !dsdlib_id_equals(&header.id, "FRM8") || + !dsdlib_id_equals(&header.format, "DSD ")) + return false; + + while (true) { + if (!dsdiff_read_chunk_header(decoder, is, + chunk_header)) + return false; + + if (dsdlib_id_equals(&chunk_header->id, "PROP")) { + if (!dsdiff_read_prop(decoder, is, metadata, + chunk_header)) + return false; + } else if (dsdlib_id_equals(&chunk_header->id, "DSD ")) { + const uint64_t chunk_size = chunk_header->GetSize(); + metadata->chunk_size = chunk_size; + return true; + } else { + /* ignore unknown chunk */ + const uint64_t chunk_size = chunk_header->GetSize(); + goffset chunk_end_offset = is->GetOffset() + + chunk_size; + + if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +/** + * Decode one "DSD" chunk. + */ +static bool +dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is, + unsigned channels, + uint64_t chunk_size) +{ + uint8_t buffer[8192]; + + const size_t sample_size = sizeof(buffer[0]); + const size_t frame_size = channels * sample_size; + const unsigned buffer_frames = sizeof(buffer) / frame_size; + const unsigned buffer_samples = buffer_frames * frame_size; + const size_t buffer_size = buffer_samples * sample_size; + + while (chunk_size > 0) { + /* see how much aligned data from the remaining chunk + fits into the local buffer */ + unsigned now_frames = buffer_frames; + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + now_frames = (unsigned)chunk_size / frame_size; + now_size = now_frames * frame_size; + } + + size_t nbytes = decoder_read(decoder, is, buffer, now_size); + if (nbytes != now_size) + return false; + + chunk_size -= nbytes; + + if (lsbitfirst) + bit_reverse_buffer(buffer, buffer + nbytes); + + const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); + switch (cmd) { + case DecoderCommand::NONE: + break; + + case DecoderCommand::START: + case DecoderCommand::STOP: + return false; + + case DecoderCommand::SEEK: + + /* Not implemented yet */ + decoder_seek_error(decoder); + break; + } + } + return dsdlib_skip(decoder, is, chunk_size); +} + +static void +dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + DsdiffMetaData metadata; + + DsdiffChunkHeader chunk_header; + /* check if it is is a proper DFF file */ + if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header)) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, error)) { + LogError(error); + return; + } + + /* calculate song time from DSD chunk size and sample frequency */ + uint64_t chunk_size = metadata.chunk_size; + float songtime = ((chunk_size / metadata.channels) * 8) / + (float) metadata.sample_rate; + + /* success: file was recognized */ + decoder_initialized(decoder, audio_format, false, songtime); + + /* every iteration of the following loop decodes one "DSD" + chunk from a DFF file */ + + while (true) { + chunk_size = chunk_header.GetSize(); + + if (dsdlib_id_equals(&chunk_header.id, "DSD ")) { + if (!dsdiff_decode_chunk(decoder, is, + metadata.channels, + chunk_size)) + break; + } else { + /* ignore other chunks */ + if (!dsdlib_skip(decoder, is, chunk_size)) + break; + } + + /* read next chunk header; the first one was read by + dsdiff_read_metadata() */ + if (!dsdiff_read_chunk_header(decoder, + is, &chunk_header)) + break; + } +} + +static bool +dsdiff_scan_stream(struct input_stream *is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + DsdiffMetaData metadata; + DsdiffChunkHeader chunk_header; + + /* First check for DFF metadata */ + if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header)) + return false; + + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, IgnoreError())) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* calculate song time and add as tag */ + unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / + metadata.sample_rate; + tag_handler_invoke_duration(handler, handler_ctx, songtime); + + /* Read additional metadata and created tags if available */ + dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, + handler, handler_ctx); + + return true; +} + +static const char *const dsdiff_suffixes[] = { + "dff", + nullptr +}; + +static const char *const dsdiff_mime_types[] = { + "application/x-dff", + nullptr +}; + +const struct decoder_plugin dsdiff_decoder_plugin = { + "dsdiff", + dsdiff_init, + nullptr, + dsdiff_stream_decode, + nullptr, + nullptr, + dsdiff_scan_stream, + nullptr, + dsdiff_suffixes, + dsdiff_mime_types, +}; diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/DsdiffDecoderPlugin.hxx new file mode 100644 index 000000000..c50605457 --- /dev/null +++ b/src/decoder/DsdiffDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DSDIFF_H +#define MPD_DECODER_DSDIFF_H + +extern const struct decoder_plugin dsdiff_decoder_plugin; + +#endif diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx new file mode 100644 index 000000000..4c4a66aaa --- /dev/null +++ b/src/decoder/DsfDecoderPlugin.cxx @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* \file + * + * This plugin decodes DSDIFF data (SACD) embedded in DSF files. + * + * The DSF code was created using the specification found here: + * http://dsd-guide.com/sonys-dsf-file-format-spec + * + * All functions common to both DSD decoders have been moved to dsdlib + */ + +#include "config.h" +#include "DsfDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "DsdLib.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +#include <unistd.h> +#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ + +struct DsfMetaData { + unsigned sample_rate, channels; + bool bitreverse; + uint64_t chunk_size; +#ifdef HAVE_ID3TAG + goffset id3_offset; + uint64_t id3_size; +#endif +}; + +struct DsfHeader { + /** DSF header id: "DSD " */ + struct dsdlib_id id; + /** DSD chunk size, including id = 28 */ + uint32_t size_low, size_high; + /** total file size */ + uint32_t fsize_low, fsize_high; + /** pointer to id3v2 metadata, should be at the end of the file */ + uint32_t pmeta_low, pmeta_high; +}; + +/** DSF file fmt chunk */ +struct DsfFmtChunk { + /** id: "fmt " */ + struct dsdlib_id id; + /** fmt chunk size, including id, normally 52 */ + uint32_t size_low, size_high; + /** version of this format = 1 */ + uint32_t version; + /** 0: DSD raw */ + uint32_t formatid; + /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */ + uint32_t channeltype; + /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */ + uint32_t channelnum; + /** sample frequency: 2822400, 5644800 */ + uint32_t sample_freq; + /** bits per sample 1 or 8 */ + uint32_t bitssample; + /** Sample count per channel in bytes */ + uint32_t scnt_low, scnt_high; + /** block size per channel = 4096 */ + uint32_t block_size; + /** reserved, should be all zero */ + uint32_t reserved; +}; + +struct DsfDataChunk { + struct dsdlib_id id; + /** "data" chunk size, includes header (id+size) */ + uint32_t size_low, size_high; +}; + +/** + * Read and parse all needed metadata chunks for DSF files. + */ +static bool +dsf_read_metadata(struct decoder *decoder, struct input_stream *is, + DsfMetaData *metadata) +{ + uint64_t chunk_size; + DsfHeader dsf_header; + if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) || + !dsdlib_id_equals(&dsf_header.id, "DSD ")) + return false; + + chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_header.size_high)) << 32) | + ((uint64_t)GUINT32_FROM_LE(dsf_header.size_low)); + + if (sizeof(dsf_header) != chunk_size) + return false; + +#ifdef HAVE_ID3TAG + uint64_t metadata_offset; + metadata_offset = (((uint64_t)GUINT32_FROM_LE(dsf_header.pmeta_high)) << 32) | + ((uint64_t)GUINT32_FROM_LE(dsf_header.pmeta_low)); +#endif + + /* read the 'fmt ' chunk of the DSF file */ + DsfFmtChunk dsf_fmt_chunk; + if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || + !dsdlib_id_equals(&dsf_fmt_chunk.id, "fmt ")) + return false; + + uint64_t fmt_chunk_size; + fmt_chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_high)) << 32) | + ((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_low)); + + if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) + return false; + + uint32_t samplefreq = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.sample_freq); + + /* for now, only support version 1 of the standard, DSD raw stereo + files with a sample freq of 2822400 Hz */ + + if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 + || dsf_fmt_chunk.channeltype != 2 + || dsf_fmt_chunk.channelnum != 2 + || samplefreq != 2822400) + return false; + + uint32_t chblksize = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.block_size); + /* according to the spec block size should always be 4096 */ + if (chblksize != 4096) + return false; + + /* read the 'data' chunk of the DSF file */ + DsfDataChunk data_chunk; + if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) || + !dsdlib_id_equals(&data_chunk.id, "data")) + return false; + + /* data size of DSF files are padded to multiple of 4096, + we use the actual data size as chunk size */ + + uint64_t data_size; + data_size = (((uint64_t)GUINT32_FROM_LE(data_chunk.size_high)) << 32) | + ((uint64_t)GUINT32_FROM_LE(data_chunk.size_low)); + data_size -= sizeof(data_chunk); + + metadata->chunk_size = data_size; + /* data_size cannot be bigger or equal to total file size */ + const uint64_t size = (uint64_t)is->GetSize(); + if (data_size >= size) + return false; + + metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; + metadata->sample_rate = samplefreq; +#ifdef HAVE_ID3TAG + /* metada_offset cannot be bigger then or equal to total file size */ + if (metadata_offset >= size) + metadata->id3_offset = 0; + else + metadata->id3_offset = (goffset) metadata_offset; +#endif + /* check bits per sample format, determine if bitreverse is needed */ + metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; + return true; +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +/** + * DSF data is build up of alternating 4096 blocks of DSD samples for left and + * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 + * block of 4096 DSD right samples to 8k of samples in normal PCM left/right + * order. + */ +static void +dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) +{ + for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { + scratch[i] = *(dest+j); + j++; + } + + for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { + scratch[i] = *(dest+4096+j); + j++; + } + + for (unsigned i = 0; i < (unsigned)nrbytes; i++) { + *dest = scratch[i]; + dest++; + } +} + +/** + * Decode one complete DSF 'data' chunk i.e. a complete song + */ +static bool +dsf_decode_chunk(struct decoder *decoder, struct input_stream *is, + unsigned channels, + uint64_t chunk_size, + bool bitreverse) +{ + uint8_t buffer[8192]; + + /* scratch buffer for DSF samples to convert to the needed + normal left/right regime of samples */ + uint8_t dsf_scratch_buffer[8192]; + + const size_t sample_size = sizeof(buffer[0]); + const size_t frame_size = channels * sample_size; + const unsigned buffer_frames = sizeof(buffer) / frame_size; + const unsigned buffer_samples = buffer_frames * frame_size; + const size_t buffer_size = buffer_samples * sample_size; + + while (chunk_size > 0) { + /* see how much aligned data from the remaining chunk + fits into the local buffer */ + unsigned now_frames = buffer_frames; + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + now_frames = (unsigned)chunk_size / frame_size; + now_size = now_frames * frame_size; + } + + size_t nbytes = decoder_read(decoder, is, buffer, now_size); + if (nbytes != now_size) + return false; + + chunk_size -= nbytes; + + if (bitreverse) + bit_reverse_buffer(buffer, buffer + nbytes); + + dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes); + + const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); + switch (cmd) { + case DecoderCommand::NONE: + break; + + case DecoderCommand::START: + case DecoderCommand::STOP: + return false; + + case DecoderCommand::SEEK: + + /* not implemented yet */ + decoder_seek_error(decoder); + break; + } + } + return dsdlib_skip(decoder, is, chunk_size); +} + +static void +dsf_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + /* check if it is a proper DSF file */ + DsfMetaData metadata; + if (!dsf_read_metadata(decoder, is, &metadata)) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, error)) { + LogError(error); + return; + } + /* Calculate song time from DSD chunk size and sample frequency */ + uint64_t chunk_size = metadata.chunk_size; + float songtime = ((chunk_size / metadata.channels) * 8) / + (float) metadata.sample_rate; + + /* success: file was recognized */ + decoder_initialized(decoder, audio_format, false, songtime); + + if (!dsf_decode_chunk(decoder, is, metadata.channels, + chunk_size, + metadata.bitreverse)) + return; +} + +static bool +dsf_scan_stream(struct input_stream *is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + /* check DSF metadata */ + DsfMetaData metadata; + if (!dsf_read_metadata(NULL, is, &metadata)) + return false; + + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, IgnoreError())) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* calculate song time and add as tag */ + unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / + metadata.sample_rate; + tag_handler_invoke_duration(handler, handler_ctx, songtime); + +#ifdef HAVE_ID3TAG + /* Add available tags from the ID3 tag */ + dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); +#endif + return true; +} + +static const char *const dsf_suffixes[] = { + "dsf", + NULL +}; + +static const char *const dsf_mime_types[] = { + "application/x-dsf", + NULL +}; + +const struct decoder_plugin dsf_decoder_plugin = { + "dsf", + nullptr, + nullptr, + dsf_stream_decode, + nullptr, + nullptr, + dsf_scan_stream, + nullptr, + dsf_suffixes, + dsf_mime_types, +}; diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/DsfDecoderPlugin.hxx new file mode 100644 index 000000000..749032d1f --- /dev/null +++ b/src/decoder/DsfDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DSF_H +#define MPD_DECODER_DSF_H + +extern const struct decoder_plugin dsf_decoder_plugin; + +#endif diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx new file mode 100644 index 000000000..de846c61a --- /dev/null +++ b/src/decoder/FaadDecoderPlugin.cxx @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FaadDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "DecoderBuffer.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <neaacdec.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> + +#define AAC_MAX_CHANNELS 6 + +static const unsigned adts_sample_rates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +static constexpr Domain faad_decoder_domain("faad_decoder"); + +/** + * Check whether the buffer head is an AAC frame, and return the frame + * length. Returns 0 if it is not a frame. + */ +static size_t +adts_check_frame(const unsigned char *data) +{ + /* check syncword */ + if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) + return 0; + + return (((unsigned int)data[3] & 0x3) << 11) | + (((unsigned int)data[4]) << 3) | + (data[5] >> 5); +} + +/** + * Find the next AAC frame in the buffer. Returns 0 if no frame is + * found or if not enough data is available. + */ +static size_t +adts_find_frame(DecoderBuffer *buffer) +{ + size_t length, frame_length; + bool ret; + + while (true) { + const uint8_t *data = (const uint8_t *) + decoder_buffer_read(buffer, &length); + if (data == nullptr || length < 8) { + /* not enough data yet */ + ret = decoder_buffer_fill(buffer); + if (!ret) + /* failed */ + return 0; + + continue; + } + + /* find the 0xff marker */ + const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length); + if (p == nullptr) { + /* no marker - discard the buffer */ + decoder_buffer_consume(buffer, length); + continue; + } + + if (p > data) { + /* discard data before 0xff */ + decoder_buffer_consume(buffer, p - data); + continue; + } + + /* is it a frame? */ + frame_length = adts_check_frame(data); + if (frame_length == 0) { + /* it's just some random 0xff byte; discard it + and continue searching */ + decoder_buffer_consume(buffer, 1); + continue; + } + + if (length < frame_length) { + /* available buffer size is smaller than the + frame will be - attempt to read more + data */ + ret = decoder_buffer_fill(buffer); + if (!ret) { + /* not enough data; discard this frame + to prevent a possible buffer + overflow */ + data = (const uint8_t *) + decoder_buffer_read(buffer, &length); + if (data != nullptr) + decoder_buffer_consume(buffer, length); + } + + continue; + } + + /* found a full frame! */ + return frame_length; + } +} + +static float +adts_song_duration(DecoderBuffer *buffer) +{ + unsigned int frames, frame_length; + unsigned sample_rate = 0; + float frames_per_second; + + /* Read all frames to ensure correct time and bitrate */ + for (frames = 0;; frames++) { + frame_length = adts_find_frame(buffer); + if (frame_length == 0) + break; + + + if (frames == 0) { + size_t buffer_length; + const uint8_t *data = (const uint8_t *) + decoder_buffer_read(buffer, &buffer_length); + assert(data != nullptr); + assert(frame_length <= buffer_length); + + sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; + } + + decoder_buffer_consume(buffer, frame_length); + } + + frames_per_second = (float)sample_rate / 1024.0; + if (frames_per_second <= 0) + return -1; + + return (float)frames / frames_per_second; +} + +static float +faad_song_duration(DecoderBuffer *buffer, struct input_stream *is) +{ + size_t fileread; + size_t tagsize; + size_t length; + bool success; + + const goffset size = is->GetSize(); + fileread = size >= 0 ? size : 0; + + decoder_buffer_fill(buffer); + const uint8_t *data = (const uint8_t *) + decoder_buffer_read(buffer, &length); + if (data == nullptr) + return -1; + + tagsize = 0; + if (length >= 10 && !memcmp(data, "ID3", 3)) { + /* skip the ID3 tag */ + + tagsize = (data[6] << 21) | (data[7] << 14) | + (data[8] << 7) | (data[9] << 0); + + tagsize += 10; + + success = decoder_buffer_skip(buffer, tagsize) && + decoder_buffer_fill(buffer); + if (!success) + return -1; + + data = (const uint8_t *)decoder_buffer_read(buffer, &length); + if (data == nullptr) + return -1; + } + + if (is->IsSeekable() && length >= 2 && + data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) { + /* obtain the duration from the ADTS header */ + float song_length = adts_song_duration(buffer); + + is->LockSeek(tagsize, SEEK_SET, IgnoreError()); + + data = (const uint8_t *)decoder_buffer_read(buffer, &length); + if (data != nullptr) + decoder_buffer_consume(buffer, length); + decoder_buffer_fill(buffer); + + return song_length; + } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { + /* obtain the duration from the ADIF header */ + unsigned bit_rate; + size_t skip_size = (data[4] & 0x80) ? 9 : 0; + + if (8 + skip_size > length) + /* not enough data yet; skip parsing this + header */ + return -1; + + bit_rate = ((data[4 + skip_size] & 0x0F) << 19) | + (data[5 + skip_size] << 11) | + (data[6 + skip_size] << 3) | + (data[7 + skip_size] & 0xE0); + + if (fileread != 0 && bit_rate != 0) + return fileread * 8.0 / bit_rate; + else + return fileread; + } else + return -1; +} + +/** + * Wrapper for NeAACDecInit() which works around some API + * inconsistencies in libfaad. + */ +static bool +faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, + AudioFormat &audio_format, Error &error) +{ + int32_t nbytes; + uint32_t sample_rate; + uint8_t channels; +#ifdef HAVE_FAAD_LONG + /* 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_p = (unsigned long *)(void *)&sample_rate; +#else + uint32_t *sample_rate_p = &sample_rate; +#endif + + size_t length; + const unsigned char *data = (const unsigned char *) + decoder_buffer_read(buffer, &length); + if (data == nullptr) { + error.Set(faad_decoder_domain, "Empty file"); + return false; + } + + nbytes = NeAACDecInit(decoder, + /* deconst hack, libfaad requires this */ + const_cast<unsigned char *>(data), + length, + sample_rate_p, &channels); + if (nbytes < 0) { + error.Set(faad_decoder_domain, "Not an AAC stream"); + return false; + } + + decoder_buffer_consume(buffer, nbytes); + + return audio_format_init_checked(audio_format, sample_rate, + SampleFormat::S16, channels, error); +} + +/** + * Wrapper for NeAACDecDecode() which works around some API + * inconsistencies in libfaad. + */ +static const void * +faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, + NeAACDecFrameInfo *frame_info) +{ + size_t length; + const unsigned char *data = (const unsigned char *) + decoder_buffer_read(buffer, &length); + if (data == nullptr) + return nullptr; + + return NeAACDecDecode(decoder, frame_info, + /* deconst hack, libfaad requires this */ + const_cast<unsigned char *>(data), + length); +} + +/** + * Get a song file's total playing time in seconds, as a float. + * Returns 0 if the duration is unknown, and a negative value if the + * file is invalid. + */ +static float +faad_get_file_time_float(struct input_stream *is) +{ + DecoderBuffer *buffer; + float length; + + buffer = decoder_buffer_new(nullptr, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + length = faad_song_duration(buffer, is); + + if (length < 0) { + bool ret; + AudioFormat audio_format; + + NeAACDecHandle decoder = NeAACDecOpen(); + + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + NeAACDecSetConfiguration(decoder, config); + + decoder_buffer_fill(buffer); + + ret = faad_decoder_init(decoder, buffer, audio_format, + IgnoreError()); + if (ret) + length = 0; + + NeAACDecClose(decoder); + } + + decoder_buffer_free(buffer); + + return length; +} + +/** + * Get a song file's total playing time in seconds, as an int. + * Returns 0 if the duration is unknown, and a negative value if the + * file is invalid. + */ +static int +faad_get_file_time(struct input_stream *is) +{ + int file_time = -1; + float length; + + if ((length = faad_get_file_time_float(is)) >= 0) + file_time = length + 0.5; + + return file_time; +} + +static void +faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) +{ + float total_time = 0; + AudioFormat audio_format; + bool ret; + uint16_t bit_rate = 0; + DecoderBuffer *buffer; + + buffer = decoder_buffer_new(mpd_decoder, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + total_time = faad_song_duration(buffer, is); + + /* create the libfaad decoder */ + + NeAACDecHandle decoder = NeAACDecOpen(); + + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + config->downMatrix = 1; + config->dontUpSampleImplicitSBR = 0; + NeAACDecSetConfiguration(decoder, config); + + while (!decoder_buffer_is_full(buffer) && !is->LockIsEOF() && + decoder_get_command(mpd_decoder) == DecoderCommand::NONE) { + adts_find_frame(buffer); + decoder_buffer_fill(buffer); + } + + /* initialize it */ + + Error error; + ret = faad_decoder_init(decoder, buffer, audio_format, error); + if (!ret) { + LogError(error); + NeAACDecClose(decoder); + return; + } + + /* initialize the MPD core */ + + decoder_initialized(mpd_decoder, audio_format, false, total_time); + + /* the decoder loop */ + + DecoderCommand cmd; + do { + size_t frame_size; + const void *decoded; + NeAACDecFrameInfo frame_info; + + /* find the next frame */ + + frame_size = adts_find_frame(buffer); + if (frame_size == 0) + /* end of file */ + break; + + /* decode it */ + + decoded = faad_decoder_decode(decoder, buffer, &frame_info); + + if (frame_info.error > 0) { + FormatWarning(faad_decoder_domain, + "error decoding AAC stream: %s", + NeAACDecGetErrorMessage(frame_info.error)); + break; + } + + if (frame_info.channels != audio_format.channels) { + FormatInfo(faad_decoder_domain, + "channel count changed from %u to %u", + audio_format.channels, frame_info.channels); + break; + } + + if (frame_info.samplerate != audio_format.sample_rate) { + FormatInfo(faad_decoder_domain, + "sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); + break; + } + + decoder_buffer_consume(buffer, frame_info.bytesconsumed); + + /* update bit rate and position */ + + if (frame_info.samples > 0) { + bit_rate = frame_info.bytesconsumed * 8.0 * + frame_info.channels * audio_format.sample_rate / + frame_info.samples / 1000 + 0.5; + } + + /* send PCM samples to MPD */ + + cmd = decoder_data(mpd_decoder, is, decoded, + (size_t)frame_info.samples * 2, + bit_rate); + } while (cmd != DecoderCommand::STOP); + + /* cleanup */ + + NeAACDecClose(decoder); +} + +static bool +faad_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + int file_time = faad_get_file_time(is); + + if (file_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, file_time); + return true; +} + +static const char *const faad_suffixes[] = { "aac", nullptr }; +static const char *const faad_mime_types[] = { + "audio/aac", "audio/aacp", nullptr +}; + +const struct decoder_plugin faad_decoder_plugin = { + "faad", + nullptr, + nullptr, + faad_stream_decode, + nullptr, + nullptr, + faad_scan_stream, + nullptr, + faad_suffixes, + faad_mime_types, +}; diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/FaadDecoderPlugin.hxx new file mode 100644 index 000000000..162c155ad --- /dev/null +++ b/src/decoder/FaadDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FAAD_DECODER_PLUGIN_HXX +#define MPD_FAAD_DECODER_PLUGIN_HXX + +extern const struct decoder_plugin faad_decoder_plugin; + +#endif diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx new file mode 100644 index 000000000..646b8f974 --- /dev/null +++ b/src/decoder/FfmpegDecoderPlugin.cxx @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "FfmpegMetaData.hxx" +#include "tag/TagHandler.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "LogV.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavformat/avio.h> +#include <libavutil/avutil.h> +#include <libavutil/log.h> +#include <libavutil/mathematics.h> +#include <libavutil/dict.h> +} + +static constexpr Domain ffmpeg_domain("ffmpeg"); + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +static LogLevel +import_ffmpeg_level(int level) +{ + if (level <= AV_LOG_FATAL) + return LogLevel::ERROR; + + if (level <= AV_LOG_WARNING) + return LogLevel::WARNING; + + if (level <= AV_LOG_INFO) + return LogLevel::INFO; + + return LogLevel::DEBUG; +} + +static void +mpd_ffmpeg_log_callback(gcc_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(ffmpeg_domain.GetName(), "/", cls->item_name(ptr), NULL); + const Domain d(domain); + LogFormatV(d, import_ffmpeg_level(level), fmt, vl); + g_free(domain); + } +} + +struct AvioStream { + struct decoder *decoder; + struct input_stream *input; + + AVIOContext *io; + + unsigned char buffer[8192]; + + AvioStream(struct decoder *_decoder, input_stream *_input) + :decoder(_decoder), input(_input), io(nullptr) {} + + ~AvioStream() { + if (io != nullptr) + av_free(io); + } + + bool Open(); +}; + +static int +mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) +{ + AvioStream *stream = (AvioStream *)opaque; + + return decoder_read(stream->decoder, stream->input, + (void *)buf, size); +} + +static int64_t +mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) +{ + AvioStream *stream = (AvioStream *)opaque; + + if (whence == AVSEEK_SIZE) + return stream->input->size; + + Error error; + if (!stream->input->LockSeek(pos, whence, error)) + return -1; + + return stream->input->offset; +} + +bool +AvioStream::Open() +{ + io = avio_alloc_context(buffer, sizeof(buffer), + false, this, + mpd_ffmpeg_stream_read, nullptr, + input->seekable + ? mpd_ffmpeg_stream_seek : nullptr); + return io != nullptr; +} + +/** + * API compatibility wrapper for av_open_input_stream() and + * avformat_open_input(). + */ +static int +mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, + AVIOContext *pb, + const char *filename, + AVInputFormat *fmt) +{ + AVFormatContext *context = avformat_alloc_context(); + if (context == NULL) + return AVERROR(ENOMEM); + + context->pb = pb; + *ic_ptr = context; + return avformat_open_input(ic_ptr, filename, fmt, NULL); +} + +static bool +ffmpeg_init(gcc_unused const config_param ¶m) +{ + av_log_set_callback(mpd_ffmpeg_log_callback); + + av_register_all(); + return true; +} + +static int +ffmpeg_find_audio_stream(const AVFormatContext *format_context) +{ + for (unsigned i = 0; i < format_context->nb_streams; ++i) + if (format_context->streams[i]->codec->codec_type == + AVMEDIA_TYPE_AUDIO) + return i; + + return -1; +} + +gcc_const +static double +time_from_ffmpeg(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) + / (double)1024; +} + +gcc_const +static int64_t +time_to_ffmpeg(double t, const AVRational time_base) +{ + return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, + time_base); +} + +static void +copy_interleave_frame2(uint8_t *dest, uint8_t **src, + unsigned nframes, unsigned nchannels, + unsigned sample_size) +{ + for (unsigned frame = 0; frame < nframes; ++frame) { + for (unsigned channel = 0; channel < nchannels; ++channel) { + memcpy(dest, src[channel] + frame * sample_size, + sample_size); + dest += sample_size; + } + } +} + +/** + * Copy PCM data from a AVFrame to an interleaved buffer. + */ +static int +copy_interleave_frame(const AVCodecContext *codec_context, + const AVFrame *frame, + uint8_t **output_buffer, + uint8_t **global_buffer, int *global_buffer_size) +{ + int plane_size; + const int data_size = + av_samples_get_buffer_size(&plane_size, + codec_context->channels, + frame->nb_samples, + codec_context->sample_fmt, 1); + if (av_sample_fmt_is_planar(codec_context->sample_fmt) && + codec_context->channels > 1) { + if(*global_buffer_size < data_size) { + av_freep(global_buffer); + + *global_buffer = (uint8_t*)av_malloc(data_size); + + if (!*global_buffer) + /* Not enough memory - shouldn't happen */ + return AVERROR(ENOMEM); + *global_buffer_size = data_size; + } + *output_buffer = *global_buffer; + copy_interleave_frame2(*output_buffer, frame->extended_data, + frame->nb_samples, + codec_context->channels, + av_get_bytes_per_sample(codec_context->sample_fmt)); + } else { + *output_buffer = frame->extended_data[0]; + } + + return data_size; +} + +static DecoderCommand +ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVRational *time_base, + AVFrame *frame, + uint8_t **buffer, int *buffer_size) +{ + if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + time_from_ffmpeg(packet->pts, *time_base)); + + AVPacket packet2 = *packet; + + uint8_t *output_buffer; + + DecoderCommand cmd = DecoderCommand::NONE; + while (packet2.size > 0 && cmd == DecoderCommand::NONE) { + int audio_size = 0; + int got_frame = 0; + int len = avcodec_decode_audio4(codec_context, + frame, &got_frame, + &packet2); + if (len >= 0 && got_frame) { + audio_size = copy_interleave_frame(codec_context, + frame, + &output_buffer, + buffer, buffer_size); + if (audio_size < 0) + len = audio_size; + } + + if (len < 0) { + /* if error, we skip the frame */ + LogInfo(ffmpeg_domain, + "decoding failed, frame skipped"); + break; + } + + packet2.data += len; + packet2.size -= len; + + if (audio_size <= 0) + continue; + + cmd = decoder_data(decoder, is, + output_buffer, audio_size, + codec_context->bit_rate / 1000); + } + return cmd; +} + +gcc_const +static SampleFormat +ffmpeg_sample_format(enum AVSampleFormat sample_fmt) +{ + switch (sample_fmt) { + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + return SampleFormat::S16; + + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + return SampleFormat::S32; + + case AV_SAMPLE_FMT_FLTP: + return SampleFormat::FLOAT; + + default: + break; + } + + char buffer[64]; + const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), + sample_fmt); + if (name != NULL) + FormatError(ffmpeg_domain, + "Unsupported libavcodec SampleFormat value: %s (%d)", + name, sample_fmt); + else + FormatError(ffmpeg_domain, + "Unsupported libavcodec SampleFormat value: %d", + sample_fmt); + return SampleFormat::UNDEFINED; +} + +static AVInputFormat * +ffmpeg_probe(struct decoder *decoder, struct input_stream *is) +{ + enum { + BUFFER_SIZE = 16384, + PADDING = 16, + }; + + Error error; + + unsigned char *buffer = (unsigned char *)g_malloc(BUFFER_SIZE); + size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); + if (nbytes <= PADDING || !is->LockSeek(0, SEEK_SET, error)) { + g_free(buffer); + return NULL; + } + + /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes + beyond the declared buffer limit, which makes valgrind + angry; this workaround removes some padding from the buffer + size */ + nbytes -= PADDING; + + AVProbeData avpd; + avpd.buf = buffer; + avpd.buf_size = nbytes; + avpd.filename = is->uri.c_str(); + + AVInputFormat *format = av_probe_input_format(&avpd, true); + g_free(buffer); + + return format; +} + +static void +ffmpeg_decode(struct decoder *decoder, struct input_stream *input) +{ + AVInputFormat *input_format = ffmpeg_probe(decoder, input); + if (input_format == NULL) + return; + + FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", + input_format->name, input_format->long_name); + + AvioStream stream(decoder, input); + if (!stream.Open()) { + LogError(ffmpeg_domain, "Failed to open stream"); + return; + } + + //ffmpeg works with ours "fileops" helper + AVFormatContext *format_context = NULL; + if (mpd_ffmpeg_open_input(&format_context, stream.io, + input->uri.c_str(), + input_format) != 0) { + LogError(ffmpeg_domain, "Open failed"); + return; + } + + const int find_result = + avformat_find_stream_info(format_context, NULL); + if (find_result < 0) { + LogError(ffmpeg_domain, "Couldn't find stream info"); + avformat_close_input(&format_context); + return; + } + + int audio_stream = ffmpeg_find_audio_stream(format_context); + if (audio_stream == -1) { + LogError(ffmpeg_domain, "No audio stream inside"); + avformat_close_input(&format_context); + return; + } + + AVStream *av_stream = format_context->streams[audio_stream]; + + AVCodecContext *codec_context = av_stream->codec; + if (codec_context->codec_name[0] != 0) + FormatDebug(ffmpeg_domain, "codec '%s'", + codec_context->codec_name); + + AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); + + if (!codec) { + LogError(ffmpeg_domain, "Unsupported audio codec"); + avformat_close_input(&format_context); + return; + } + + const SampleFormat sample_format = + ffmpeg_sample_format(codec_context->sample_fmt); + if (sample_format == SampleFormat::UNDEFINED) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + codec_context->sample_rate, + sample_format, + codec_context->channels, error)) { + LogError(error); + avformat_close_input(&format_context); + return; + } + + /* the audio format must be read from AVCodecContext by now, + because avcodec_open() has been demonstrated to fill bogus + values into AVCodecContext.channels - a change that will be + reverted later by avcodec_decode_audio3() */ + + const int open_result = avcodec_open2(codec_context, codec, NULL); + if (open_result < 0) { + LogError(ffmpeg_domain, "Could not open codec"); + avformat_close_input(&format_context); + return; + } + + int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE + ? format_context->duration / AV_TIME_BASE + : 0; + + decoder_initialized(decoder, audio_format, + input->seekable, total_time); + + AVFrame *frame = avcodec_alloc_frame(); + if (!frame) { + LogError(ffmpeg_domain, "Could not allocate frame"); + avformat_close_input(&format_context); + return; + } + + uint8_t *interleaved_buffer = NULL; + int interleaved_buffer_size = 0; + + DecoderCommand cmd; + do { + AVPacket packet; + if (av_read_frame(format_context, &packet) < 0) + /* end of file */ + break; + + if (packet.stream_index == audio_stream) + cmd = ffmpeg_send_packet(decoder, input, + &packet, codec_context, + &av_stream->time_base, + frame, + &interleaved_buffer, &interleaved_buffer_size); + else + cmd = decoder_get_command(decoder); + + av_free_packet(&packet); + + if (cmd == DecoderCommand::SEEK) { + int64_t where = + time_to_ffmpeg(decoder_seek_where(decoder), + av_stream->time_base); + + if (av_seek_frame(format_context, audio_stream, where, + AV_TIME_BASE) < 0) + decoder_seek_error(decoder); + else { + avcodec_flush_buffers(codec_context); + decoder_command_finished(decoder); + } + } + } while (cmd != DecoderCommand::STOP); + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) + avcodec_free_frame(&frame); +#else + av_freep(&frame); +#endif + av_freep(&interleaved_buffer); + + avcodec_close(codec_context); + avformat_close_input(&format_context); +} + +//no tag reading in ffmpeg, check if playable +static bool +ffmpeg_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + AVInputFormat *input_format = ffmpeg_probe(NULL, is); + if (input_format == NULL) + return false; + + AvioStream stream(nullptr, is); + if (!stream.Open()) + return false; + + AVFormatContext *f = NULL; + if (mpd_ffmpeg_open_input(&f, stream.io, is->uri.c_str(), + input_format) != 0) + return false; + + const int find_result = + avformat_find_stream_info(f, NULL); + if (find_result < 0) { + avformat_close_input(&f); + return false; + } + + if (f->duration != (int64_t)AV_NOPTS_VALUE) + tag_handler_invoke_duration(handler, handler_ctx, + f->duration / AV_TIME_BASE); + + ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); + int idx = ffmpeg_find_audio_stream(f); + if (idx >= 0) + ffmpeg_scan_dictionary(f->streams[idx]->metadata, + handler, handler_ctx); + + avformat_close_input(&f); + return true; +} + +/** + * A list of extensions found for the formats supported by ffmpeg. + * This list is current as of 02-23-09; To find out if there are more + * supported formats, check the ffmpeg changelog since this date for + * more formats. + */ +static const char *const ffmpeg_suffixes[] = { + "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", + "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", + "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", + "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", + "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", + "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", + "m4a", "m4b", "m4v", + "mad", + "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", + "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", + "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", + "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", + "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", + "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", + "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", + "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", + "wve", + NULL +}; + +static const char *const ffmpeg_mime_types[] = { + "application/flv", + "application/m4a", + "application/mp4", + "application/octet-stream", + "application/ogg", + "application/x-ms-wmz", + "application/x-ms-wmd", + "application/x-ogg", + "application/x-shockwave-flash", + "application/x-shorten", + "audio/8svx", + "audio/16sv", + "audio/aac", + "audio/ac3", + "audio/aiff" + "audio/amr", + "audio/basic", + "audio/flac", + "audio/m4a", + "audio/mp4", + "audio/mpeg", + "audio/musepack", + "audio/ogg", + "audio/qcelp", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-8svx", + "audio/x-16sv", + "audio/x-aac", + "audio/x-ac3", + "audio/x-aiff" + "audio/x-alaw", + "audio/x-au", + "audio/x-dca", + "audio/x-eac3", + "audio/x-flac", + "audio/x-gsm", + "audio/x-mace", + "audio/x-matroska", + "audio/x-monkeys-audio", + "audio/x-mpeg", + "audio/x-ms-wma", + "audio/x-ms-wax", + "audio/x-musepack", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + "audio/x-pn-realaudio", + "audio/x-pn-multirate-realaudio", + "audio/x-speex", + "audio/x-tta" + "audio/x-voc", + "audio/x-wav", + "audio/x-wma", + "audio/x-wv", + "video/anim", + "video/quicktime", + "video/msvideo", + "video/ogg", + "video/theora", + "video/webm", + "video/x-dv", + "video/x-flv", + "video/x-matroska", + "video/x-mjpeg", + "video/x-mpeg", + "video/x-ms-asf", + "video/x-msvideo", + "video/x-ms-wmv", + "video/x-ms-wvx", + "video/x-ms-wm", + "video/x-ms-wmx", + "video/x-nut", + "video/x-pva", + "video/x-theora", + "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 +}; + +const struct decoder_plugin ffmpeg_decoder_plugin = { + "ffmpeg", + ffmpeg_init, + nullptr, + ffmpeg_decode, + nullptr, + nullptr, + ffmpeg_scan_stream, + nullptr, + ffmpeg_suffixes, + ffmpeg_mime_types +}; diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/FfmpegDecoderPlugin.hxx new file mode 100644 index 000000000..9a637fff0 --- /dev/null +++ b/src/decoder/FfmpegDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FFMPEG_HXX +#define MPD_DECODER_FFMPEG_HXX + +extern const struct decoder_plugin ffmpeg_decoder_plugin; + +#endif diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx new file mode 100644 index 000000000..9965e4d28 --- /dev/null +++ b/src/decoder/FfmpegMetaData.cxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegMetaData.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagHandler.hxx" + +static const struct tag_table ffmpeg_tags[] = { + { "year", TAG_DATE }, + { "author-sort", TAG_ARTIST_SORT }, + { "album_artist", TAG_ALBUM_ARTIST }, + { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, + + /* sentinel */ + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +ffmpeg_copy_metadata(enum tag_type type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *mt = NULL; + + while ((mt = av_dict_get(m, name, mt, 0)) != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + type, mt->value); +} + +static void +ffmpeg_scan_pairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *i = NULL; + + while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL) + tag_handler_invoke_pair(handler, handler_ctx, + i->key, i->value); +} + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + ffmpeg_copy_metadata(tag_type(i), dict, tag_item_names[i], + handler, handler_ctx); + + for (const struct tag_table *i = ffmpeg_tags; + i->name != NULL; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + + if (handler->pair != NULL) + ffmpeg_scan_pairs(dict, handler, handler_ctx); +} diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx new file mode 100644 index 000000000..0fd73df04 --- /dev/null +++ b/src/decoder/FfmpegMetaData.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_METADATA_HXX +#define MPD_FFMPEG_METADATA_HXX + +extern "C" { +#include <libavformat/avformat.h> +#include <libavutil/avutil.h> +#include <libavutil/dict.h> +} + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +struct tag_handler; + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx new file mode 100644 index 000000000..6bafeb9c2 --- /dev/null +++ b/src/decoder/FlacCommon.cxx @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#include "config.h" +#include "FlacCommon.hxx" +#include "FlacMetadata.hxx" +#include "FlacPcm.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +flac_data::flac_data(struct decoder *_decoder, + struct input_stream *_input_stream) + :FlacInput(_input_stream, _decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream) +{ +} + +static SampleFormat +flac_sample_format(unsigned bits_per_sample) +{ + switch (bits_per_sample) { + case 8: + return SampleFormat::S8; + + case 16: + return SampleFormat::S16; + + case 24: + return SampleFormat::S24_P32; + + case 32: + return SampleFormat::S32; + + default: + return SampleFormat::UNDEFINED; + } +} + +static void +flac_got_stream_info(struct flac_data *data, + const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + if (data->initialized || data->unsupported) + return; + + Error error; + if (!audio_format_init_checked(data->audio_format, + stream_info->sample_rate, + flac_sample_format(stream_info->bits_per_sample), + stream_info->channels, error)) { + LogError(error); + data->unsupported = true; + return; + } + + data->frame_size = data->audio_format.GetFrameSize(); + + 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) +{ + if (data->unsupported) + return; + + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + flac_got_stream_info(data, &block->data.stream_info); + break; + + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + if (flac_parse_replay_gain(&rgi, block)) + decoder_replay_gain(data->decoder, &rgi); + + if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) + decoder_mixramp(data->decoder, + mixramp_start, mixramp_end); + + flac_vorbis_comments_to_tag(data->tag, + &block->data.vorbis_comment); + + default: + break; + } +} + +/** + * 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 bool +flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) +{ + if (data->unsupported) + return false; + + Error error; + if (!audio_format_init_checked(data->audio_format, + header->sample_rate, + flac_sample_format(header->bits_per_sample), + header->channels, error)) { + LogError(error); + data->unsupported = true; + return false; + } + + data->frame_size = data->audio_format.GetFrameSize(); + + decoder_initialized(data->decoder, data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); + + data->initialized = true; + + return true; +} + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes) +{ + void *buffer; + unsigned bit_rate; + + if (!data->initialized && !flac_got_first_frame(data, &frame->header)) + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + size_t buffer_size = frame->header.blocksize * data->frame_size; + buffer = data->buffer.Get(buffer_size); + + flac_convert(buffer, frame->header.channels, + data->audio_format.format, buf, + 0, frame->header.blocksize); + + if (nbytes > 0) + bit_rate = nbytes * 8 * frame->header.sample_rate / + (1000 * frame->header.blocksize); + else + bit_rate = 0; + + auto cmd = decoder_data(data->decoder, data->input_stream, + buffer, buffer_size, + bit_rate); + data->next_frame += frame->header.blocksize; + switch (cmd) { + case DecoderCommand::NONE: + case DecoderCommand::START: + break; + + case DecoderCommand::STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + case DecoderCommand::SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx new file mode 100644 index 000000000..726e9de92 --- /dev/null +++ b/src/decoder/FlacCommon.hxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX + +#include "FlacInput.hxx" +#include "DecoderAPI.hxx" +#include "pcm/PcmBuffer.hxx" + +#include <FLAC/stream_decoder.h> +#include <FLAC/metadata.h> + +struct flac_data : public FlacInput { + PcmBuffer 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. + */ + AudioFormat audio_format; + + /** + * 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; + + /** + * 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; + + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; + + FLAC__uint64 position; + + struct decoder *decoder; + struct input_stream *input_stream; + + Tag tag; + + flac_data(struct decoder *decoder, struct input_stream *input_stream); +}; + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + struct flac_data *data); + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx new file mode 100644 index 000000000..7ce44febd --- /dev/null +++ b/src/decoder/FlacDecoderPlugin.cxx @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "FlacDecoderPlugin.h" +#include "FlacDomain.hxx" +#include "FlacCommon.hxx" +#include "FlacMetadata.hxx" +#include "OggCodec.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + 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: + case FLAC__STREAM_DECODER_SEEK_ERROR: + case FLAC__STREAM_DECODER_ABORTED: + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + case FLAC__STREAM_DECODER_UNINITIALIZED: + break; + } + + LogError(flac_domain, FLAC__StreamDecoderStateString[state]); +} + +static void flacMetadata(gcc_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 bool +flac_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.Read(file)) { + FormatDebug(flac_domain, + "Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.Read(is)) { + FormatDebug(flac_domain, + "Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == nullptr) { + LogError(flac_domain, + "FLAC__stream_decoder_new() failed"); + return nullptr; + } + + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + LogDebug(flac_domain, + "FLAC__stream_decoder_set_metadata_respond() has failed"); + + 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)) { + LogWarning(flac_domain, "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; + + data->first_frame = t_start; + + while (true) { + DecoderCommand cmd; + if (!data->tag.IsEmpty()) { + cmd = decoder_tag(data->decoder, data->input_stream, + std::move(data->tag)); + data->tag.Clear(); + } else + cmd = decoder_get_command(decoder); + + if (cmd == DecoderCommand::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 == DecoderCommand::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) && + decoder_get_command(decoder) == DecoderCommand::NONE) { + /* a failure that was not triggered by a + decoder command */ + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + break; + } + } +} + +static FLAC__StreamDecoderInitStatus +stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_ogg_stream(flac_dec, + FlacInput::Read, + FlacInput::Seek, + FlacInput::Tell, + FlacInput::Length, + FlacInput::Eof, + flac_write_cb, + flacMetadata, + FlacInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_stream(flac_dec, + FlacInput::Read, + FlacInput::Seek, + FlacInput::Tell, + FlacInput::Length, + FlacInput::Eof, + flac_write_cb, + flacMetadata, + FlacInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) +{ + return is_ogg + ? stream_init_oggflac(flac_dec, data) + : stream_init_flac(flac_dec, data); +} + +static void +flac_decode_internal(struct decoder * decoder, + struct input_stream *input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + + flac_dec = flac_decoder_new(); + if (flac_dec == nullptr) + return; + + struct flac_data data(decoder, input_stream); + + FLAC__StreamDecoderInitStatus status = + stream_init(flac_dec, &data, is_ogg); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + FLAC__stream_decoder_delete(flac_dec); + LogWarning(flac_domain, + FLAC__StreamDecoderInitStatusString[status]); + return; + } + + if (!flac_decoder_initialize(&data, flac_dec, 0)) { + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); +} + +static void +flac_decode(struct decoder * decoder, struct input_stream *input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +static bool +oggflac_init(gcc_unused const config_param ¶m) +{ + return !!FLAC_API_SUPPORTS_OGG_FLAC; +} + +static bool +oggflac_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.ReadOgg(file)) { + FormatDebug(flac_domain, + "Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +oggflac_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.ReadOgg(is)) { + FormatDebug(flac_domain, + "Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static void +oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream->LockSeek(0, SEEK_SET, IgnoreError()); + + flac_decode_internal(decoder, input_stream, true); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-flac+ogg", + "audio/x-ogg", + nullptr +}; + +const struct decoder_plugin oggflac_decoder_plugin = { + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, +}; + +static const char *const flac_suffixes[] = { "flac", nullptr }; +static const char *const flac_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + nullptr +}; + +const struct decoder_plugin flac_decoder_plugin = { + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, +}; diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/FlacDecoderPlugin.h new file mode 100644 index 000000000..c99deeef7 --- /dev/null +++ b/src/decoder/FlacDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_FLAC_H +#define MPD_DECODER_FLAC_H + +extern const struct decoder_plugin flac_decoder_plugin; +extern const struct decoder_plugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/FlacDomain.cxx new file mode 100644 index 000000000..2bc91079e --- /dev/null +++ b/src/decoder/FlacDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FlacDomain.hxx" +#include "util/Domain.hxx" + +const Domain flac_domain("flac"); diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/FlacDomain.hxx new file mode 100644 index 000000000..8d5b825ed --- /dev/null +++ b/src/decoder/FlacDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_DOMAIN_HXX +#define MPD_FLAC_DOMAIN_HXX + +#include "check.h" + +extern const class Domain flac_domain; + +#endif diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/FlacIOHandle.cxx new file mode 100644 index 000000000..77da864e5 --- /dev/null +++ b/src/decoder/FlacIOHandle.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FlacIOHandle.hxx" +#include "util/Error.hxx" +#include "gcc.h" + +#include <errno.h> + +static size_t +FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + uint8_t *const p0 = (uint8_t *)ptr, *p = p0, + *const end = p0 + size * nmemb; + + /* libFLAC is very picky about short reads, and expects the IO + callback to fill the whole buffer (undocumented!) */ + + Error error; + while (p < end) { + size_t nbytes = is->LockRead(p, end - p, error); + if (nbytes == 0) { + if (!error.IsDefined()) + /* end of file */ + break; + + if (error.IsDomain(errno_domain)) + errno = error.GetCode(); + else + /* just some random non-zero + errno value */ + errno = EINVAL; + return 0; + } + + p += nbytes; + } + + /* libFLAC expects a clean errno after returning from the IO + callbacks (undocumented!) */ + errno = 0; + return (p - p0) / size; +} + +static int +FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +{ + input_stream *is = (input_stream *)handle; + + Error error; + return is->LockSeek(offset, whence, error) ? 0 : -1; +} + +static FLAC__int64 +FlacIOTell(FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + return is->offset; +} + +static int +FlacIOEof(FLAC__IOHandle handle) +{ + input_stream *is = (input_stream *)handle; + + return is->LockIsEOF(); +} + +static int +FlacIOClose(gcc_unused FLAC__IOHandle handle) +{ + /* no-op because the libFLAC caller is repsonsible for closing + the #input_stream */ + + return 0; +} + +const FLAC__IOCallbacks flac_io_callbacks = { + FlacIORead, + nullptr, + nullptr, + nullptr, + FlacIOEof, + FlacIOClose, +}; + +const FLAC__IOCallbacks flac_io_callbacks_seekable = { + FlacIORead, + nullptr, + FlacIOSeek, + FlacIOTell, + FlacIOEof, + FlacIOClose, +}; diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/FlacIOHandle.hxx new file mode 100644 index 000000000..3216dafa4 --- /dev/null +++ b/src/decoder/FlacIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_IO_HANDLE_HXX +#define MPD_FLAC_IO_HANDLE_HXX + +#include "gcc.h" +#include "InputStream.hxx" + +#include <FLAC/callback.h> + +extern const FLAC__IOCallbacks flac_io_callbacks; +extern const FLAC__IOCallbacks flac_io_callbacks_seekable; + +static inline FLAC__IOHandle +ToFlacIOHandle(input_stream *is) +{ + return (FLAC__IOHandle)is; +} + +static inline const FLAC__IOCallbacks & +GetFlacIOCallbacks(const input_stream *is) +{ + return is->seekable + ? flac_io_callbacks_seekable + : flac_io_callbacks; +} + +#endif diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx new file mode 100644 index 000000000..88b942971 --- /dev/null +++ b/src/decoder/FlacInput.cxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FlacInput.hxx" +#include "FlacDomain.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "gcc.h" + +FLAC__StreamDecoderReadStatus +FlacInput::Read(FLAC__byte buffer[], size_t *bytes) +{ + size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); + *bytes = r; + + if (r == 0) { + if (input_stream->LockIsEOF() || + (decoder != nullptr && + decoder_get_command(decoder) != DecoderCommand::NONE)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +FLAC__StreamDecoderSeekStatus +FlacInput::Seek(FLAC__uint64 absolute_byte_offset) +{ + if (!input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + ::Error error; + if (!input_stream->LockSeek(absolute_byte_offset, SEEK_SET, error)) { + LogError(error); + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + } + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus +FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) +{ + if (!input_stream->seekable) + return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; + + *absolute_byte_offset = (FLAC__uint64)input_stream->offset; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus +FlacInput::Length(FLAC__uint64 *stream_length) +{ + if (input_stream->size < 0) + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *stream_length = (FLAC__uint64)input_stream->size; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +FLAC__bool +FlacInput::Eof() +{ + return (decoder != nullptr && + decoder_get_command(decoder) != DecoderCommand::NONE && + decoder_get_command(decoder) != DecoderCommand::SEEK) || + input_stream->LockIsEOF(); +} + +void +FlacInput::Error(FLAC__StreamDecoderErrorStatus status) +{ + if (decoder == nullptr || + decoder_get_command(decoder) != DecoderCommand::STOP) + LogWarning(flac_domain, + FLAC__StreamDecoderErrorStatusString[status]); +} + +FLAC__StreamDecoderReadStatus +FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, + void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Read(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus +FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Seek(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus +FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Tell(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus +FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Length(stream_length); +} + +FLAC__bool +FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder, + void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Eof(); +} + +void +FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + i->Error(status); +} + diff --git a/src/decoder/FlacInput.hxx b/src/decoder/FlacInput.hxx new file mode 100644 index 000000000..8fc69f960 --- /dev/null +++ b/src/decoder/FlacInput.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_INPUT_HXX +#define MPD_FLAC_INPUT_HXX + +#include <FLAC/stream_decoder.h> + +/** + * This class wraps an #input_stream in libFLAC stream decoder + * callbacks. + */ +class FlacInput { + struct decoder *decoder; + + struct input_stream *input_stream; + +public: + FlacInput(struct input_stream *_input_stream, + struct decoder *_decoder=nullptr) + :decoder(_decoder), input_stream(_input_stream) {} + +protected: + FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); + FLAC__bool Eof(); + void Error(FLAC__StreamDecoderErrorStatus status); + +public: + static FLAC__StreamDecoderReadStatus + Read(const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, void *client_data); + + static FLAC__StreamDecoderSeekStatus + Seek(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderTellStatus + Tell(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderLengthStatus + Length(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data); + + static FLAC__bool + Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); + + static void + Error(const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data); +}; + +#endif diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/FlacMetadata.cxx new file mode 100644 index 000000000..078d0c081 --- /dev/null +++ b/src/decoder/FlacMetadata.cxx @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FlacMetadata.hxx" +#include "XiphTags.hxx" +#include "tag/Tag.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagBuilder.hxx" +#include "ReplayGainInfo.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.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 = nullptr; + 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, size_t *length_r) +{ + size_t name_length = strlen(name); + const char *comment = (const char*)entry->entry; + + if (entry->length <= name_length || + g_ascii_strncasecmp(comment, name, name_length) != 0) + return nullptr; + + if (comment[name_length] == '=') { + *length_r = entry->length - name_length - 1; + return comment + name_length + 1; + } + + return nullptr; +} + +/** + * 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(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, enum tag_type tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + size_t value_length; + + value = flac_comment_value(entry, name, &value_length); + if (value != nullptr) { + char *p = g_strndup(value, value_length); + tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); + g_free(p); + return true; + } + + return false; +} + +static void +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != nullptr) { + char *name = g_strdup((const char*)entry->entry); + char *value = strchr(name, '='); + + if (value != nullptr && value > name) { + *value++ = 0; + tag_handler_invoke_pair(handler, handler_ctx, + name, value); + } + + g_free(name); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (flac_copy_comment(entry, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (flac_copy_comment(entry, + tag_item_names[i], (enum tag_type)i, + handler, handler_ctx)) + return; +} + +static void +flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < comment->num_comments; ++i) + flac_scan_comment(&comment->comments[i], + handler, handler_ctx); +} + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx) +{ + switch (block->type) { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flac_scan_comments(&block->data.vorbis_comment, + handler, handler_ctx); + break; + + case FLAC__METADATA_TYPE_STREAMINFO: + if (block->data.stream_info.sample_rate > 0) + tag_handler_invoke_duration(handler, handler_ctx, + flac_duration(&block->data.stream_info)); + break; + + default: + break; + } +} + +void +flac_vorbis_comments_to_tag(Tag &tag, + const FLAC__StreamMetadata_VorbisComment *comment) +{ + TagBuilder tag_builder; + flac_scan_comments(comment, &add_tag_handler, &tag_builder); + tag_builder.Commit(tag); +} + +void +FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataIterator iterator(*this); + + do { + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) + break; + + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); +} diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/FlacMetadata.hxx new file mode 100644 index 000000000..57769672f --- /dev/null +++ b/src/decoder/FlacMetadata.hxx @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_METADATA_H +#define MPD_FLAC_METADATA_H + +#include "gcc.h" +#include "FlacIOHandle.hxx" + +#include <FLAC/metadata.h> + +#include <assert.h> + +class FlacMetadataChain { + FLAC__Metadata_Chain *chain; + +public: + FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {} + + ~FlacMetadataChain() { + ::FLAC__metadata_chain_delete(chain); + } + + explicit operator FLAC__Metadata_Chain *() { + return chain; + } + + bool Read(const char *path) { + return ::FLAC__metadata_chain_read(chain, path); + } + + bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_with_callbacks(chain, + handle, + callbacks); + } + + bool Read(input_stream *is) { + return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); + } + + bool ReadOgg(const char *path) { + return ::FLAC__metadata_chain_read_ogg(chain, path); + } + + bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain, + handle, + callbacks); + } + + bool ReadOgg(input_stream *is) { + return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); + } + + gcc_pure + FLAC__Metadata_ChainStatus GetStatus() const { + return ::FLAC__metadata_chain_status(chain); + } + + gcc_pure + const char *GetStatusString() const { + return FLAC__Metadata_ChainStatusString[GetStatus()]; + } + + void Scan(const struct tag_handler *handler, void *handler_ctx); +}; + +class FLACMetadataIterator { + FLAC__Metadata_Iterator *iterator; + +public: + FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {} + + FLACMetadataIterator(FlacMetadataChain &chain) + :iterator(::FLAC__metadata_iterator_new()) { + ::FLAC__metadata_iterator_init(iterator, + (FLAC__Metadata_Chain *)chain); + } + + ~FLACMetadataIterator() { + ::FLAC__metadata_iterator_delete(iterator); + } + + bool Next() { + return ::FLAC__metadata_iterator_next(iterator); + } + + gcc_pure + FLAC__StreamMetadata *GetBlock() { + return ::FLAC__metadata_iterator_get_block(iterator); + } +}; + +struct tag_handler; +struct Tag; +struct replay_gain_info; + +static inline unsigned +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + assert(stream_info->sample_rate > 0); + + return (stream_info->total_samples + stream_info->sample_rate - 1) / + stream_info->sample_rate; +} + +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(Tag &tag, + const FLAC__StreamMetadata_VorbisComment *comment); + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/FlacPcm.cxx new file mode 100644 index 000000000..ff855fa70 --- /dev/null +++ b/src/decoder/FlacPcm.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FlacPcm.hxx" + +#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, SampleFormat sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) +{ + switch (sample_format) { + case SampleFormat::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 SampleFormat::S24_P32: + case SampleFormat::S32: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::S8: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::FLOAT: + case SampleFormat::DSD: + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + } +} diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/FlacPcm.hxx new file mode 100644 index 000000000..fa85f65dd --- /dev/null +++ b/src/decoder/FlacPcm.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX + +#include "AudioFormat.hxx" + +#include <FLAC/ordinals.h> + +void +flac_convert(void *dest, + unsigned int num_channels, SampleFormat sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end); + +#endif diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/FluidsynthDecoderPlugin.cxx new file mode 100644 index 000000000..99f1598c8 --- /dev/null +++ b/src/decoder/FluidsynthDecoderPlugin.cxx @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FluidsynthDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <fluidsynth.h> + +static constexpr Domain fluidsynth_domain("fluidsynth"); + +static unsigned sample_rate; +static const char *soundfont_path; + +/** + * Convert a fluidsynth log level to a GLib log level. + */ +static LogLevel +fluidsynth_level_to_mpd(enum fluid_log_level level) +{ + switch (level) { + case FLUID_PANIC: + case FLUID_ERR: + return LogLevel::ERROR; + + case FLUID_WARN: + return LogLevel::WARNING; + + case FLUID_INFO: + return LogLevel::INFO; + + case FLUID_DBG: + case LAST_LOG_LEVEL: + return LogLevel::DEBUG; + } + + /* invalid fluidsynth log level */ + return LogLevel::INFO; +} + +/** + * The fluidsynth logging callback. It forwards messages to the GLib + * logging library. + */ +static void +fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data) +{ + Log(fluidsynth_domain, + fluidsynth_level_to_mpd(fluid_log_level(level)), + message); +} + +static bool +fluidsynth_init(const config_param ¶m) +{ + Error error; + + sample_rate = param.GetBlockValue("sample_rate", 48000u); + if (!audio_check_sample_rate(sample_rate, error)) { + LogError(error); + return false; + } + + soundfont_path = param.GetBlockValue("soundfont", + "/usr/share/sounds/sf2/FluidR3_GM.sf2"); + + fluid_set_log_function(LAST_LOG_LEVEL, + fluidsynth_mpd_log_function, nullptr); + + return true; +} + +static void +fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) +{ + char setting_sample_rate[] = "synth.sample-rate"; + /* + char setting_verbose[] = "synth.verbose"; + char setting_yes[] = "yes"; + */ + fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; + int ret; + + /* set up fluid settings */ + + settings = new_fluid_settings(); + if (settings == nullptr) + return; + + fluid_settings_setnum(settings, setting_sample_rate, sample_rate); + + /* + fluid_settings_setstr(settings, setting_verbose, setting_yes); + */ + + /* create the fluid synth */ + + synth = new_fluid_synth(settings); + if (synth == nullptr) { + delete_fluid_settings(settings); + return; + } + + ret = fluid_synth_sfload(synth, soundfont_path, true); + if (ret < 0) { + LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed"); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* create the fluid player */ + + player = new_fluid_player(synth); + if (player == nullptr) { + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + ret = fluid_player_add(player, path_fs); + if (ret != 0) { + LogWarning(fluidsynth_domain, "fluid_player_add() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* start the player */ + + ret = fluid_player_play(player); + if (ret != 0) { + LogWarning(fluidsynth_domain, "fluid_player_play() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* initialization complete - announce the audio format to the + MPD core */ + + const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); + decoder_initialized(decoder, audio_format, false, -1); + + DecoderCommand cmd; + while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { + int16_t buffer[2048]; + const unsigned max_frames = G_N_ELEMENTS(buffer) / 2; + + /* read samples from fluidsynth and send them to the + MPD core */ + + ret = fluid_synth_write_s16(synth, max_frames, + buffer, 0, 2, + buffer, 1, 2); + if (ret != 0) + break; + + cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer), + 0); + if (cmd != DecoderCommand::NONE) + break; + } + + /* clean up */ + + fluid_player_stop(player); + fluid_player_join(player); + + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); +} + +static bool +fluidsynth_scan_file(const char *file, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + return fluid_is_midifile(file); +} + +static const char *const fluidsynth_suffixes[] = { + "mid", + nullptr +}; + +const struct decoder_plugin fluidsynth_decoder_plugin = { + "fluidsynth", + fluidsynth_init, + nullptr, + nullptr, + fluidsynth_file_decode, + fluidsynth_scan_file, + nullptr, + nullptr, + fluidsynth_suffixes, + nullptr, +}; diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/FluidsynthDecoderPlugin.hxx new file mode 100644 index 000000000..40ed7e4d8 --- /dev/null +++ b/src/decoder/FluidsynthDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FLUIDSYNTH_HXX +#define MPD_DECODER_FLUIDSYNTH_HXX + +extern const struct decoder_plugin fluidsynth_decoder_plugin; + +#endif diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx new file mode 100644 index 000000000..bbcc9618a --- /dev/null +++ b/src/decoder/GmeDecoderPlugin.cxx @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "GmeDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <gme/gme.h> + +#define SUBTUNE_PREFIX "tune_" + +static constexpr Domain gme_domain("gme"); + +static constexpr unsigned GME_SAMPLE_RATE = 44100; +static constexpr unsigned GME_CHANNELS = 2; +static constexpr unsigned GME_BUFFER_FRAMES = 2048; +static constexpr unsigned 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, nullptr); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + g_free(pat); + if (!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, nullptr)) { + g_pattern_spec_free(path_with_subtune); + return path_container; + } + + char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if (ptr != nullptr) + *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, nullptr); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + g_free(pat); + + if (g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, nullptr)) { + 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, nullptr, 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 = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return nullptr; + } + + const unsigned num_songs = gme_track_count(emu); + /* if it only contains a single tune, don't treat as container */ + if (num_songs < 2) + return nullptr; + + 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 nullptr; +} + +static void +gme_file_decode(struct decoder *decoder, const char *path_fs) +{ + char *path_container = get_container_name(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + g_free(path_container); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return; + } + + gme_info_t *ti; + const int song_num = get_song_num(path_fs); + gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + gme_delete(emu); + return; + } + + const float song_len = ti->length > 0 + ? ti->length / 1000.0 + : -1.0; + + /* initialize the MPD decoder */ + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE, + SampleFormat::S16, GME_CHANNELS, + error)) { + LogError(error); + gme_free_info(ti); + gme_delete(emu); + return; + } + + decoder_initialized(decoder, audio_format, true, song_len); + + gme_err = gme_start_track(emu, song_num); + if (gme_err != nullptr) + LogWarning(gme_domain, gme_err); + + if (ti->length > 0) + gme_set_fade(emu, ti->length); + + /* play */ + DecoderCommand cmd; + do { + short buf[GME_BUFFER_SAMPLES]; + gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return; + } + + cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0); + if (cmd == DecoderCommand::SEEK) { + float where = decoder_seek_where(decoder); + gme_err = gme_seek(emu, int(where * 1000)); + if (gme_err != nullptr) + LogWarning(gme_domain, gme_err); + decoder_command_finished(decoder); + } + + if (gme_track_ended(emu)) + break; + } while (cmd != DecoderCommand::STOP); + + gme_free_info(ti); + gme_delete(emu); +} + +static bool +gme_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + char *path_container = get_container_name(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + g_free(path_container); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return false; + } + + const int song_num = get_song_num(path_fs); + + gme_info_t *ti; + gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + gme_delete(emu); + return false; + } + + assert(ti != nullptr); + + if (ti->length > 0) + tag_handler_invoke_duration(handler, handler_ctx, + ti->length / 100); + + if (ti->song != nullptr) { + if (gme_track_count(emu) > 1) { + /* start numbering subtunes from 1 */ + char *tag_title = + g_strdup_printf("%s (%d/%d)", + ti->song, song_num + 1, + gme_track_count(emu)); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + g_free(tag_title); + } else + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, ti->song); + } + + if (ti->author != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ARTIST, ti->author); + + if (ti->game != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ALBUM, ti->game); + + if (ti->comment != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_COMMENT, ti->comment); + + if (ti->copyright != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_DATE, ti->copyright); + + gme_free_info(ti); + gme_delete(emu); + + return true; +} + +static const char *const gme_suffixes[] = { + "ay", "gbs", "gym", "hes", "kss", "nsf", + "nsfe", "sap", "spc", "vgm", "vgz", + nullptr +}; + +extern const struct decoder_plugin gme_decoder_plugin; +const struct decoder_plugin gme_decoder_plugin = { + "gme", + nullptr, + nullptr, + nullptr, + gme_file_decode, + gme_scan_file, + nullptr, + gme_container_scan, + gme_suffixes, + nullptr, +}; diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/GmeDecoderPlugin.hxx new file mode 100644 index 000000000..fba735d92 --- /dev/null +++ b/src/decoder/GmeDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_GME_HXX +#define MPD_DECODER_GME_HXX + +extern const struct decoder_plugin gme_decoder_plugin; + +#endif diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx new file mode 100644 index 000000000..1cce24f31 --- /dev/null +++ b/src/decoder/MadDecoderPlugin.cxx @@ -0,0 +1,1184 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MadDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "ConfigGlobal.hxx" +#include "tag/TagId3.hxx" +#include "tag/TagRva2.hxx" +#include "tag/TagHandler.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <glib.h> +#include <mad.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#define FRAMES_CUSHION 2000 + +#define READ_BUFFER_SIZE 40960 + +enum mp3_action { + DECODE_SKIP = -3, + DECODE_BREAK = -2, + DECODE_CONT = -1, + DECODE_OK = 0 +}; + +enum muteframe { + MUTEFRAME_NONE, + MUTEFRAME_SKIP, + MUTEFRAME_SEEK +}; + +/* the number of samples of silence the decoder inserts at start */ +#define DECODERDELAY 529 + +#define DEFAULT_GAPLESS_MP3_PLAYBACK true + +static constexpr Domain mad_domain("mad"); + +static bool gapless_playback; + +static inline int32_t +mad_fixed_to_24_sample(mad_fixed_t sample) +{ + enum { + bits = 24, + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + /* round */ + sample = sample + (1L << (MAD_F_FRACBITS - bits)); + + /* clip */ + if (gcc_unlikely(sample > MAX)) + sample = MAX; + else if (gcc_unlikely(sample < MIN)) + sample = MIN; + + /* quantize */ + return sample >> (MAD_F_FRACBITS + 1 - bits); +} + +static void +mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, + unsigned int start, unsigned int end, + unsigned int num_channels) +{ + unsigned int i, c; + + for (i = start; i < end; ++i) { + for (c = 0; c < num_channels; ++c) + *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); + } +} + +static bool +mp3_plugin_init(gcc_unused const config_param ¶m) +{ + gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, + DEFAULT_GAPLESS_MP3_PLAYBACK); + return true; +} + +#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 + +struct MadDecoder { + struct mad_stream stream; + struct mad_frame frame; + struct mad_synth synth; + mad_timer_t timer; + unsigned char input_buffer[READ_BUFFER_SIZE]; + int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + float total_time; + float elapsed_time; + float seek_where; + enum muteframe mute_frame; + long *frame_offsets; + mad_timer_t *times; + unsigned long highest_frame; + unsigned long max_frames; + unsigned long current_frame; + unsigned int drop_start_frames; + 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; + unsigned long bit_rate; + struct decoder *decoder; + struct input_stream *input_stream; + enum mad_layer layer; + + MadDecoder(struct decoder *decoder, struct input_stream *input_stream); + ~MadDecoder(); + + bool Seek(long offset); + bool FillBuffer(); + void ParseId3(size_t tagsize, Tag **mpd_tag); + enum mp3_action DecodeNextFrameHeader(Tag **tag); + enum mp3_action DecodeNextFrame(); + + gcc_pure + goffset ThisFrameOffset() const; + + gcc_pure + goffset RestIncludingThisFrame() const; + + /** + * Attempt to calulcate the length of the song from filesize + */ + void FileSizeToSongLength(); + + bool DecodeFirstFrame(Tag **tag); + + gcc_pure + long TimeToFrame(double t) const; + + void UpdateTimerNextFrame(); + + /** + * Sends the synthesized current frame via decoder_data(). + */ + DecoderCommand SendPCM(unsigned i, unsigned pcm_length); + + /** + * Synthesize the current frame and send it via + * decoder_data(). + */ + DecoderCommand SyncAndSend(); + + bool Read(); +}; + +MadDecoder::MadDecoder(struct decoder *_decoder, + struct input_stream *_input_stream) + :mute_frame(MUTEFRAME_NONE), + frame_offsets(nullptr), + times(nullptr), + highest_frame(0), max_frames(0), current_frame(0), + drop_start_frames(0), drop_end_frames(0), + drop_start_samples(0), drop_end_samples(0), + found_replay_gain(false), found_xing(false), + found_first_frame(false), decoded_first_frame(false), + decoder(_decoder), input_stream(_input_stream), + layer(mad_layer(0)) +{ + mad_stream_init(&stream); + mad_stream_options(&stream, MAD_OPTION_IGNORECRC); + mad_frame_init(&frame); + mad_synth_init(&synth); + mad_timer_reset(&timer); +} + +inline bool +MadDecoder::Seek(long offset) +{ + Error error; + if (!input_stream->LockSeek(offset, SEEK_SET, error)) + return false; + + mad_stream_buffer(&stream, input_buffer, 0); + stream.error = MAD_ERROR_NONE; + + return true; +} + +inline bool +MadDecoder::FillBuffer() +{ + size_t remaining, length; + unsigned char *dest; + + if (stream.next_frame != nullptr) { + remaining = stream.bufend - stream.next_frame; + memmove(input_buffer, stream.next_frame, remaining); + dest = input_buffer + remaining; + length = READ_BUFFER_SIZE - remaining; + } else { + remaining = 0; + length = READ_BUFFER_SIZE; + dest = input_buffer; + } + + /* we've exhausted the read buffer, so give up!, these potential + * mp3 frames are way too big, and thus unlikely to be mp3 frames */ + if (length == 0) + return false; + + length = decoder_read(decoder, input_stream, dest, length); + if (length == 0) + return false; + + mad_stream_buffer(&stream, input_buffer, length + remaining); + stream.error = MAD_ERROR_NONE; + + return true; +} + +#ifdef HAVE_ID3TAG +static bool +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; + + replay_gain_info_init(replay_gain_info); + + 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, "replaygain_track_gain") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = atof(value); + found = true; + } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value); + found = true; + } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = atof(value); + found = true; + } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value); + found = true; + } + + free(key); + free(value); + } + + return found || + /* fall back on RVA2 if no replaygain tags found */ + 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 = nullptr; + *mixramp_end = nullptr; + + 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); + } + + return found; +} +#endif + +inline void +MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) +{ +#ifdef HAVE_ID3TAG + struct id3_tag *id3_tag = nullptr; + id3_length_t count; + id3_byte_t const *id3_data; + id3_byte_t *allocated = nullptr; + + count = stream.bufend - stream.this_frame; + + if (tagsize <= count) { + id3_data = stream.this_frame; + mad_stream_skip(&(stream), tagsize); + } else { + allocated = (id3_byte_t *)g_malloc(tagsize); + memcpy(allocated, stream.this_frame, count); + mad_stream_skip(&(stream), count); + + while (count < tagsize) { + size_t len; + + len = decoder_read(decoder, input_stream, + allocated + count, tagsize - count); + if (len == 0) + break; + else + count += len; + } + + if (count != tagsize) { + LogDebug(mad_domain, "error parsing ID3 tag"); + g_free(allocated); + return; + } + + id3_data = allocated; + } + + id3_tag = id3_tag_parse(id3_data, tagsize); + if (id3_tag == nullptr) { + g_free(allocated); + return; + } + + if (mpd_tag) { + Tag *tmp_tag = tag_id3_import(id3_tag); + if (tmp_tag != nullptr) { + delete *mpd_tag; + *mpd_tag = tmp_tag; + } + } + + if (decoder != nullptr) { + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + + if (parse_id3_replay_gain_info(&rgi, id3_tag)) { + decoder_replay_gain(decoder, &rgi); + found_replay_gain = true; + } + + if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) + decoder_mixramp(decoder, mixramp_start, mixramp_end); + } + + id3_tag_delete(id3_tag); + + g_free(allocated); +#else /* !HAVE_ID3TAG */ + (void)mpd_tag; + + /* This code is enabled when libid3tag is disabled. Instead + of parsing the ID3 frame, it just skips it. */ + + size_t count = stream.bufend - stream.this_frame; + + if (tagsize <= count) { + mad_stream_skip(&stream, tagsize); + } else { + mad_stream_skip(&stream, count); + + while (count < tagsize) { + size_t len = tagsize - count; + char ignored[1024]; + if (len > sizeof(ignored)) + len = sizeof(ignored); + + len = decoder_read(decoder, input_stream, + ignored, len); + if (len == 0) + break; + else + count += len; + } + } +#endif +} + +#ifndef HAVE_ID3TAG +/** + * This function emulates libid3tag when it is disabled. Instead of + * doing a real analyzation of the frame, it just checks whether the + * frame begins with the string "ID3". If so, it returns the length + * of the ID3 frame. + */ +static signed long +id3_tag_query(const void *p0, size_t length) +{ + const char *p = (const char *)p0; + + return length >= 10 && memcmp(p, "ID3", 3) == 0 + ? (p[8] << 7) + p[9] + 10 + : 0; +} +#endif /* !HAVE_ID3TAG */ + +enum mp3_action +MadDecoder::DecodeNextFrameHeader(Tag **tag) +{ + if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && + !FillBuffer()) + return DECODE_BREAK; + + if (mad_header_decode(&frame.header, &stream)) { + if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) { + signed long tagsize = id3_tag_query(stream.this_frame, + stream.bufend - + stream.this_frame); + + if (tagsize > 0) { + if (tag && !(*tag)) { + ParseId3((size_t)tagsize, tag); + } else { + mad_stream_skip(&stream, tagsize); + } + return DECODE_CONT; + } + } + if (MAD_RECOVERABLE(stream.error)) { + return DECODE_SKIP; + } else { + if (stream.error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + FormatWarning(mad_domain, + "unrecoverable frame level error: %s", + mad_stream_errorstr(&stream)); + return DECODE_BREAK; + } + } + } + + enum mad_layer new_layer = frame.header.layer; + if (layer == (mad_layer)0) { + if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) { + /* Only layer 2 and 3 have been tested to work */ + return DECODE_SKIP; + } + + layer = new_layer; + } else if (new_layer != layer) { + /* Don't decode frames with a different layer than the first */ + return DECODE_SKIP; + } + + return DECODE_OK; +} + +enum mp3_action +MadDecoder::DecodeNextFrame() +{ + if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && + !FillBuffer()) + return DECODE_BREAK; + + if (mad_frame_decode(&frame, &stream)) { + if (stream.error == MAD_ERROR_LOSTSYNC) { + signed long tagsize = id3_tag_query(stream.this_frame, + stream.bufend - + stream.this_frame); + if (tagsize > 0) { + mad_stream_skip(&stream, tagsize); + return DECODE_CONT; + } + } + if (MAD_RECOVERABLE(stream.error)) { + return DECODE_SKIP; + } else { + if (stream.error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + FormatWarning(mad_domain, + "unrecoverable frame level error: %s", + mad_stream_errorstr(&stream)); + return DECODE_BREAK; + } + } + } + + return DECODE_OK; +} + +/* xing stuff stolen from alsaplayer, and heavily modified by jat */ +#define XI_MAGIC (('X' << 8) | 'i') +#define NG_MAGIC (('n' << 8) | 'g') +#define IN_MAGIC (('I' << 8) | 'n') +#define FO_MAGIC (('f' << 8) | 'o') + +enum xing_magic { + XING_MAGIC_XING, /* VBR */ + XING_MAGIC_INFO /* CBR */ +}; + +struct xing { + long flags; /* valid fields (see below) */ + unsigned long frames; /* total number of frames */ + unsigned long bytes; /* total number of bytes */ + unsigned char toc[100]; /* 100-point seek table */ + long scale; /* VBR quality */ + enum xing_magic magic; /* header magic */ +}; + +enum { + XING_FRAMES = 0x00000001L, + XING_BYTES = 0x00000002L, + XING_TOC = 0x00000004L, + XING_SCALE = 0x00000008L +}; + +struct lame_version { + unsigned major; + unsigned minor; +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ + 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 */ + int encoder_delay; /* # of added samples at start of mp3 */ + int encoder_padding; /* # of added samples at end of mp3 */ + int crc; /* CRC of the first 190 bytes of this frame */ +}; + +static bool +parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) +{ + unsigned long bits; + int bitlen; + int bitsleft; + int i; + + bitlen = *oldbitlen; + + if (bitlen < 16) + return false; + + bits = mad_bit_read(ptr, 16); + bitlen -= 16; + + if (bits == XI_MAGIC) { + if (bitlen < 16) + return false; + + if (mad_bit_read(ptr, 16) != NG_MAGIC) + return false; + + bitlen -= 16; + xing->magic = XING_MAGIC_XING; + } else if (bits == IN_MAGIC) { + if (bitlen < 16) + return false; + + if (mad_bit_read(ptr, 16) != FO_MAGIC) + return false; + + bitlen -= 16; + xing->magic = XING_MAGIC_INFO; + } + else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; + else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; + else + return false; + + if (bitlen < 32) + return false; + xing->flags = mad_bit_read(ptr, 32); + bitlen -= 32; + + if (xing->flags & XING_FRAMES) { + if (bitlen < 32) + return false; + xing->frames = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_BYTES) { + if (bitlen < 32) + return false; + xing->bytes = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_TOC) { + if (bitlen < 800) + return false; + for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); + bitlen -= 800; + } + + if (xing->flags & XING_SCALE) { + if (bitlen < 32) + return false; + xing->scale = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + /* Make sure we consume no less than 120 bytes (960 bits) in hopes that + * the LAME tag is found there, and not right after the Xing header */ + bitsleft = 960 - ((*oldbitlen) - bitlen); + if (bitsleft < 0) + return false; + else if (bitsleft > 0) { + mad_bit_read(ptr, bitsleft); + bitlen -= bitsleft; + } + + *oldbitlen = bitlen; + + return true; +} + +static bool +parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) +{ + int adj = 0; + int name; + int orig; + int sign; + int gain; + int i; + + /* Unlike the xing header, the lame tag has a fixed length. Fail if + * not all 36 bytes (288 bits) are there. */ + if (*bitlen < 288) + return false; + + for (i = 0; i < 9; i++) + lame->encoder[i] = (char)mad_bit_read(ptr, 8); + lame->encoder[9] = '\0'; + + *bitlen -= 72; + + /* This is technically incorrect, since the encoder might not be lame. + * But there's no other way to determine if this is a lame tag, and we + * wouldn't want to go reading a tag that's not there. */ + if (!g_str_has_prefix(lame->encoder, "LAME")) + return false; + + if (sscanf(lame->encoder+4, "%u.%u", + &lame->version.major, &lame->version.minor) != 2) + return false; + + FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")", + lame->version.major, lame->version.minor, lame->encoder); + + /* The reference volume was changed from the 83dB used in the + * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older + * versions, since everyone else uses 89dB instead of 83dB. + * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so + * it's impossible to make the proper adjustment for 3.95. + * Fortunately, 3.95 was only out for about a day before 3.95.1 was + * released. -- tmz */ + if (lame->version.major < 3 || + (lame->version.major == 3 && lame->version.minor < 95)) + adj = 6; + + mad_bit_read(ptr, 16); + + lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ + FormatDebug(mad_domain, "LAME peak found: %f", lame->peak); + + lame->track_gain = 0; + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 1 && orig != 0) { + lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; + FormatDebug(mad_domain, "LAME track gain found: %f", + lame->track_gain); + } + + /* tmz reports that this isn't currently written by any version of lame + * (as of 3.97). Since we have no way of testing it, don't use it. + * Wouldn't want to go blowing someone's ears just because we read it + * wrong. :P -- jat */ + lame->album_gain = 0; +#if 0 + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 2 && orig != 0) { + lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj; + FormatDebug(mad_domain, "LAME album gain found: %f", + lame->track_gain); + } +#else + mad_bit_read(ptr, 16); +#endif + + mad_bit_read(ptr, 16); + + lame->encoder_delay = mad_bit_read(ptr, 12); + lame->encoder_padding = mad_bit_read(ptr, 12); + + FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i", + lame->encoder_delay, lame->encoder_padding); + + mad_bit_read(ptr, 80); + + lame->crc = mad_bit_read(ptr, 16); + + *bitlen -= 216; + + return true; +} + +static inline float +mp3_frame_duration(const struct mad_frame *frame) +{ + return mad_timer_count(frame->header.duration, + MAD_UNITS_MILLISECONDS) / 1000.0; +} + +inline goffset +MadDecoder::ThisFrameOffset() const +{ + goffset offset = input_stream->GetOffset(); + + if (stream.this_frame != nullptr) + offset -= stream.bufend - stream.this_frame; + else + offset -= stream.bufend - stream.buffer; + + return offset; +} + +inline goffset +MadDecoder::RestIncludingThisFrame() const +{ + return input_stream->GetSize() - ThisFrameOffset(); +} + +inline void +MadDecoder::FileSizeToSongLength() +{ + goffset rest = RestIncludingThisFrame(); + + if (rest > 0) { + float frame_duration = mp3_frame_duration(&frame); + + total_time = (rest * 8.0) / frame.header.bitrate; + max_frames = total_time / frame_duration + FRAMES_CUSHION; + } else { + max_frames = FRAMES_CUSHION; + total_time = 0; + } +} + +inline bool +MadDecoder::DecodeFirstFrame(Tag **tag) +{ + struct xing xing; + struct lame lame; + struct mad_bitptr ptr; + int bitlen; + enum mp3_action ret; + + /* stfu gcc */ + memset(&xing, 0, sizeof(struct xing)); + xing.flags = 0; + + while (true) { + do { + ret = DecodeNextFrameHeader(tag); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_SKIP) continue; + + do { + ret = DecodeNextFrame(); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_OK) break; + } + + ptr = stream.anc_ptr; + bitlen = stream.anc_bitlen; + + FileSizeToSongLength(); + + /* + * if an xing tag exists, use that! + */ + if (parse_xing(&xing, &ptr, &bitlen)) { + found_xing = true; + mute_frame = MUTEFRAME_SKIP; + + if ((xing.flags & XING_FRAMES) && xing.frames) { + mad_timer_t duration = frame.header.duration; + mad_timer_multiply(&duration, xing.frames); + total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; + max_frames = xing.frames; + } + + if (parse_lame(&lame, &ptr, &bitlen)) { + if (gapless_playback && input_stream->IsSeekable()) { + drop_start_samples = lame.encoder_delay + + DECODERDELAY; + drop_end_samples = lame.encoder_padding; + } + + /* Album gain isn't currently used. See comment in + * parse_lame() for details. -- jat */ + if (decoder != nullptr && !found_replay_gain && + lame.track_gain) { + 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(decoder, &rgi); + } + } + } + + if (!max_frames) + return false; + + if (max_frames > 8 * 1024 * 1024) { + FormatWarning(mad_domain, + "mp3 file header indicates too many frames: %lu", + max_frames); + return false; + } + + frame_offsets = new long[max_frames]; + times = new mad_timer_t[max_frames]; + + return true; +} + +MadDecoder::~MadDecoder() +{ + mad_synth_finish(&synth); + mad_frame_finish(&frame); + mad_stream_finish(&stream); + + delete[] frame_offsets; + delete[] times; +} + +/* this is primarily used for getting total time for tags */ +static int +mad_decoder_total_file_time(struct input_stream *is) +{ + MadDecoder data(nullptr, is); + return data.DecodeFirstFrame(nullptr) + ? data.total_time + 0.5 + : -1; +} + +long +MadDecoder::TimeToFrame(double t) const +{ + unsigned long i; + + for (i = 0; i < highest_frame; ++i) { + double frame_time = + mad_timer_count(times[i], + MAD_UNITS_MILLISECONDS) / 1000.; + if (frame_time >= t) + break; + } + + return i; +} + +void +MadDecoder::UpdateTimerNextFrame() +{ + if (current_frame >= highest_frame) { + /* record this frame's properties in frame_offsets + (for seeking) and times */ + bit_rate = frame.header.bitrate; + + if (current_frame >= max_frames) + /* cap current_frame */ + current_frame = max_frames - 1; + else + highest_frame++; + + frame_offsets[current_frame] = ThisFrameOffset(); + + mad_timer_add(&timer, frame.header.duration); + times[current_frame] = timer; + } else + /* get the new timer value from "times" */ + timer = times[current_frame]; + + current_frame++; + elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0; +} + +DecoderCommand +MadDecoder::SendPCM(unsigned i, unsigned pcm_length) +{ + unsigned max_samples; + + max_samples = sizeof(output_buffer) / + sizeof(output_buffer[0]) / + MAD_NCHANNELS(&frame.header); + + while (i < pcm_length) { + unsigned int num_samples = pcm_length - i; + if (num_samples > max_samples) + num_samples = max_samples; + + i += num_samples; + + mad_fixed_to_24_buffer(output_buffer, &synth, + i - num_samples, i, + MAD_NCHANNELS(&frame.header)); + num_samples *= MAD_NCHANNELS(&frame.header); + + auto cmd = decoder_data(decoder, input_stream, output_buffer, + sizeof(output_buffer[0]) * num_samples, + bit_rate / 1000); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +inline DecoderCommand +MadDecoder::SyncAndSend() +{ + mad_synth_frame(&synth, &frame); + + if (!found_first_frame) { + unsigned int samples_per_frame = synth.pcm.length; + drop_start_frames = drop_start_samples / samples_per_frame; + drop_end_frames = drop_end_samples / samples_per_frame; + drop_start_samples = drop_start_samples % samples_per_frame; + drop_end_samples = drop_end_samples % samples_per_frame; + found_first_frame = true; + } + + if (drop_start_frames > 0) { + drop_start_frames--; + return DecoderCommand::NONE; + } else if ((drop_end_frames > 0) && + (current_frame == (max_frames + 1 - drop_end_frames))) { + /* stop decoding, effectively dropping all remaining + frames */ + return DecoderCommand::STOP; + } + + unsigned i = 0; + if (!decoded_first_frame) { + i = drop_start_samples; + decoded_first_frame = true; + } + + unsigned pcm_length = synth.pcm.length; + if (drop_end_samples && + (current_frame == max_frames - drop_end_frames)) { + if (drop_end_samples >= pcm_length) + pcm_length = 0; + else + pcm_length -= drop_end_samples; + } + + auto cmd = SendPCM(i, pcm_length); + if (cmd != DecoderCommand::NONE) + return cmd; + + if (drop_end_samples && + (current_frame == max_frames - drop_end_frames)) + /* stop decoding, effectively dropping + * all remaining samples */ + return DecoderCommand::STOP; + + return DecoderCommand::NONE; +} + +inline bool +MadDecoder::Read() +{ + enum mp3_action ret; + + UpdateTimerNextFrame(); + + switch (mute_frame) { + DecoderCommand cmd; + + case MUTEFRAME_SKIP: + mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_SEEK: + if (elapsed_time >= seek_where) + mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_NONE: + cmd = SyncAndSend(); + if (cmd == DecoderCommand::SEEK) { + unsigned long j; + + assert(input_stream->IsSeekable()); + + j = TimeToFrame(decoder_seek_where(decoder)); + if (j < highest_frame) { + if (Seek(frame_offsets[j])) { + current_frame = j; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else { + seek_where = decoder_seek_where(decoder); + mute_frame = MUTEFRAME_SEEK; + decoder_command_finished(decoder); + } + } else if (cmd != DecoderCommand::NONE) + return false; + } + + while (true) { + bool skip = false; + + do { + Tag *tag = nullptr; + + ret = DecodeNextFrameHeader(&tag); + + if (tag != nullptr) { + decoder_tag(decoder, input_stream, + std::move(*tag)); + delete tag; + } + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + else if (ret == DECODE_SKIP) + skip = true; + + if (mute_frame == MUTEFRAME_NONE) { + do { + ret = DecodeNextFrame(); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + } + + if (!skip && ret == DECODE_OK) + break; + } + + return ret != DECODE_BREAK; +} + +static void +mp3_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + MadDecoder data(decoder, input_stream); + + Tag *tag = nullptr; + if (!data.DecodeFirstFrame(&tag)) { + delete tag; + + if (decoder_get_command(decoder) == DecoderCommand::NONE) + LogError(mad_domain, + "Input does not appear to be a mp3 bit stream"); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + data.frame.header.samplerate, + SampleFormat::S24_P32, + MAD_NCHANNELS(&data.frame.header), + error)) { + LogError(error); + delete tag; + return; + } + + decoder_initialized(decoder, audio_format, + input_stream->IsSeekable(), + data.total_time); + + if (tag != nullptr) { + decoder_tag(decoder, input_stream, std::move(*tag)); + delete tag; + } + + while (data.Read()) {} +} + +static bool +mad_decoder_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + int total_time; + + total_time = mad_decoder_total_file_time(is); + if (total_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; +} + +static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr }; +static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr }; + +const struct decoder_plugin mad_decoder_plugin = { + "mad", + mp3_plugin_init, + nullptr, + mp3_decode, + nullptr, + nullptr, + mad_decoder_scan_stream, + nullptr, + mp3_suffixes, + mp3_mime_types, +}; diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/MadDecoderPlugin.hxx new file mode 100644 index 000000000..c7a77304c --- /dev/null +++ b/src/decoder/MadDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MAD_HXX +#define MPD_DECODER_MAD_HXX + +extern const struct decoder_plugin mad_decoder_plugin; + +#endif diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx new file mode 100644 index 000000000..fb82eb732 --- /dev/null +++ b/src/decoder/MikmodDecoderPlugin.cxx @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MikmodDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <mikmod.h> +#include <assert.h> + +static constexpr Domain mikmod_domain("mikmod"); + +/* this is largely copied from alsaplayer */ + +static constexpr size_t MIKMOD_FRAME_SIZE = 4096; + +static BOOL +mikmod_mpd_init(void) +{ + return VC_Init(); +} + +static void +mikmod_mpd_exit(void) +{ + VC_Exit(); +} + +static void +mikmod_mpd_update(void) +{ +} + +static BOOL +mikmod_mpd_is_present(void) +{ + return true; +} + +static char drv_name[] = PACKAGE_NAME; +static char drv_version[] = VERSION; + +#if (LIBMIKMOD_VERSION > 0x030106) +static char drv_alias[] = PACKAGE; +#endif + +static MDRIVER drv_mpd = { + nullptr, + drv_name, + drv_version, + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + drv_alias, +#if (LIBMIKMOD_VERSION >= 0x030200) + nullptr, /* CmdLineHelp */ +#endif + nullptr, /* CommandLine */ +#endif + mikmod_mpd_is_present, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + mikmod_mpd_init, + mikmod_mpd_exit, + nullptr, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + mikmod_mpd_update, + nullptr, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + +static unsigned mikmod_sample_rate; + +static bool +mikmod_decoder_init(const config_param ¶m) +{ + static char params[] = ""; + + mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u); + if (!audio_valid_sample_rate(mikmod_sample_rate)) + FormatFatalError("Invalid sample rate in line %d: %u", + param.line, mikmod_sample_rate); + + md_device = 0; + md_reverb = 0; + + MikMod_RegisterDriver(&drv_mpd); + MikMod_RegisterAllLoaders(); + + md_pansep = 64; + md_mixfreq = mikmod_sample_rate; + md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | + DMODE_16BITS); + + if (MikMod_Init(params)) { + FormatError(mikmod_domain, + "Could not init MikMod: %s", + MikMod_strerror(MikMod_errno)); + return false; + } + + return true; +} + +static void +mikmod_decoder_finish(void) +{ + MikMod_Exit(); +} + +static void +mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) +{ + char *path2; + MODULE *handle; + int ret; + SBYTE buffer[MIKMOD_FRAME_SIZE]; + + path2 = g_strdup(path_fs); + handle = Player_Load(path2, 128, 0); + g_free(path2); + + if (handle == nullptr) { + FormatError(mikmod_domain, + "failed to open mod: %s", path_fs); + return; + } + + /* Prevent module from looping forever */ + handle->loop = 0; + + const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, false, 0); + + Player_Start(handle); + + DecoderCommand cmd = DecoderCommand::NONE; + while (cmd == DecoderCommand::NONE && Player_Active()) { + ret = VC_WriteBytes(buffer, sizeof(buffer)); + cmd = decoder_data(decoder, nullptr, buffer, ret, 0); + } + + Player_Stop(); + Player_Free(handle); +} + +static bool +mikmod_decoder_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + char *path2 = g_strdup(path_fs); + MODULE *handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + g_free(path2); + FormatDebug(mikmod_domain, + "Failed to open file: %s", path_fs); + return false; + + } + + Player_Free(handle); + + char *title = Player_LoadTitle(path2); + g_free(path2); + + if (title != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); +#if (LIBMIKMOD_VERSION >= 0x030200) + MikMod_free(title); +#else + free(title); +#endif + } + + return true; +} + +static const char *const mikmod_decoder_suffixes[] = { + "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + nullptr +}; + +const struct decoder_plugin mikmod_decoder_plugin = { + "mikmod", + mikmod_decoder_init, + mikmod_decoder_finish, + nullptr, + mikmod_decoder_file_decode, + mikmod_decoder_scan_file, + nullptr, + nullptr, + mikmod_decoder_suffixes, + nullptr, +}; diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/MikmodDecoderPlugin.hxx new file mode 100644 index 000000000..dd3b1389e --- /dev/null +++ b/src/decoder/MikmodDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MIKMOD_HXX +#define MPD_DECODER_MIKMOD_HXX + +extern const struct decoder_plugin mikmod_decoder_plugin; + +#endif diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx new file mode 100644 index 000000000..39c366492 --- /dev/null +++ b/src/decoder/ModplugDecoderPlugin.cxx @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ModplugDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "tag/TagHandler.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <libmodplug/modplug.h> + +#include <glib.h> + +#include <assert.h> + +static constexpr Domain modplug_domain("modplug"); + +static constexpr size_t MODPLUG_FRAME_SIZE = 4096; +static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024; +static constexpr size_t MODPLUG_READ_BLOCK = 128 * 1024; +static constexpr goffset MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; + +static GByteArray * +mod_loadfile(struct decoder *decoder, struct input_stream *is) +{ + const goffset size = is->GetSize(); + + if (size == 0) { + LogWarning(modplug_domain, "file is empty"); + return nullptr; + } + + if (size > MODPLUG_FILE_LIMIT) { + LogWarning(modplug_domain, "file too large"); + return nullptr; + } + + //known/unknown size, preallocate array, lets read in chunks + GByteArray *bdatas; + if (size > 0) { + bdatas = g_byte_array_sized_new(size); + } else { + bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK); + } + + unsigned char *data = (unsigned char *)g_malloc(MODPLUG_READ_BLOCK); + + while (true) { + size_t ret = decoder_read(decoder, is, data, + MODPLUG_READ_BLOCK); + if (ret == 0) { + if (is->LockIsEOF()) + /* end of file */ + break; + + /* I/O error - skip this song */ + g_free(data); + g_byte_array_free(bdatas, true); + return nullptr; + } + + if (goffset(bdatas->len + ret) > MODPLUG_FILE_LIMIT) { + LogWarning(modplug_domain, "stream too large"); + g_free(data); + g_byte_array_free(bdatas, TRUE); + return nullptr; + } + + g_byte_array_append(bdatas, data, ret); + } + + g_free(data); + + return bdatas; +} + +static void +mod_decode(struct decoder *decoder, struct input_stream *is) +{ + ModPlugFile *f; + ModPlug_Settings settings; + GByteArray *bdatas; + int ret; + char audio_buffer[MODPLUG_FRAME_SIZE]; + + bdatas = mod_loadfile(decoder, is); + + if (!bdatas) { + LogWarning(modplug_domain, "could not load stream"); + return; + } + + ModPlug_GetSettings(&settings); + /* alter setting */ + settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ + settings.mChannels = 2; + settings.mBits = 16; + settings.mFrequency = 44100; + /* insert more setting changes here */ + ModPlug_SetSettings(&settings); + + f = ModPlug_Load(bdatas->data, bdatas->len); + g_byte_array_free(bdatas, TRUE); + if (!f) { + LogWarning(modplug_domain, "could not decode stream"); + return; + } + + static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, + is->IsSeekable(), + ModPlug_GetLength(f) / 1000.0); + + DecoderCommand cmd; + do { + ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); + if (ret <= 0) + break; + + cmd = decoder_data(decoder, nullptr, + audio_buffer, ret, + 0); + + if (cmd == DecoderCommand::SEEK) { + float where = decoder_seek_where(decoder); + + ModPlug_Seek(f, (int)(where * 1000.0)); + + decoder_command_finished(decoder); + } + + } while (cmd != DecoderCommand::STOP); + + ModPlug_Unload(f); +} + +static bool +modplug_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + ModPlugFile *f; + GByteArray *bdatas; + + bdatas = mod_loadfile(nullptr, is); + if (!bdatas) + return false; + + f = ModPlug_Load(bdatas->data, bdatas->len); + g_byte_array_free(bdatas, TRUE); + if (f == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + ModPlug_GetLength(f) / 1000); + + const char *title = ModPlug_GetName(f); + if (title != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); + + ModPlug_Unload(f); + + return true; +} + +static const char *const mod_suffixes[] = { + "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it", + "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm", + "ult", "umx", "xm", + nullptr +}; + +const struct decoder_plugin modplug_decoder_plugin = { + "modplug", + nullptr, + nullptr, + mod_decode, + nullptr, + nullptr, + modplug_scan_stream, + nullptr, + mod_suffixes, + nullptr, +}; diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/ModplugDecoderPlugin.hxx new file mode 100644 index 000000000..fefb02b05 --- /dev/null +++ b/src/decoder/ModplugDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MODPLUG_HXX +#define MPD_DECODER_MODPLUG_HXX + +extern const struct decoder_plugin modplug_decoder_plugin; + +#endif diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx new file mode 100644 index 000000000..c0785accc --- /dev/null +++ b/src/decoder/MpcdecDecoderPlugin.cxx @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MpcdecDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mpc/mpcdec.h> + +#include <glib.h> +#include <assert.h> +#include <unistd.h> +#include <math.h> + +struct mpc_decoder_data { + struct input_stream *is; + struct decoder *decoder; +}; + +static constexpr Domain mpcdec_domain("mpcdec"); + +static mpc_int32_t +mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return decoder_read(data->decoder, data->is, ptr, size); +} + +static mpc_bool_t +mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is->LockSeek(offset, SEEK_SET, IgnoreError()); +} + +static mpc_int32_t +mpc_tell_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return (long)data->is->GetOffset(); +} + +static mpc_bool_t +mpc_canseek_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is->IsSeekable(); +} + +static mpc_int32_t +mpc_getsize_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is->GetSize(); +} + +/* this _looks_ performance-critical, don't de-inline -- eric */ +static inline int32_t +mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) +{ + /* only doing 16-bit audio for now */ + int32_t val; + + enum { + bits = 24, + }; + + const int clip_min = -1 << (bits - 1); + const int clip_max = (1 << (bits - 1)) - 1; + +#ifdef MPC_FIXED_POINT + const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; + + if (shift < 0) + val = sample >> -shift; + else + val = sample << shift; +#else + const int float_scale = 1 << (bits - 1); + + val = sample * float_scale; +#endif + + if (val < clip_min) + val = clip_min; + else if (val > clip_max) + val = clip_max; + + return val; +} + +static void +mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src, + unsigned num_samples) +{ + while (num_samples-- > 0) + *dest++ = mpc_to_mpd_sample(*src++); +} + +static void +mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) +{ + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + struct mpc_decoder_data data; + data.is = is; + data.decoder = mpd_decoder; + + mpc_reader reader; + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_demux *demux = mpc_demux_init(&reader); + if (demux == nullptr) { + if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP) + LogWarning(mpcdec_domain, + "Not a valid musepack stream"); + return; + } + + mpc_streaminfo info; + mpc_demux_get_info(demux, &info); + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, info.sample_freq, + SampleFormat::S24_P32, + info.channels, error)) { + LogError(error); + mpc_demux_exit(demux); + return; + } + + struct replay_gain_info replay_gain_info; + replay_gain_info_init(&replay_gain_info); + 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; + + decoder_replay_gain(mpd_decoder, &replay_gain_info); + + decoder_initialized(mpd_decoder, audio_format, + is->IsSeekable(), + mpc_streaminfo_get_length(&info)); + + DecoderCommand cmd = DecoderCommand::NONE; + do { + if (cmd == DecoderCommand::SEEK) { + mpc_int64_t where = decoder_seek_where(mpd_decoder) * + audio_format.sample_rate; + bool success; + + success = mpc_demux_seek_sample(demux, where) + == MPC_STATUS_OK; + if (success) + decoder_command_finished(mpd_decoder); + else + decoder_seek_error(mpd_decoder); + } + + mpc_uint32_t vbr_update_bits = 0; + + mpc_frame_info frame; + frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; + mpc_status status = mpc_demux_decode(demux, &frame); + if (status != MPC_STATUS_OK) { + LogWarning(mpcdec_domain, + "Failed to decode sample"); + break; + } + + if (frame.bits == -1) + break; + + mpc_uint32_t ret = frame.samples; + ret *= info.channels; + + int32_t chunk[G_N_ELEMENTS(sample_buffer)]; + mpc_to_mpd_buffer(chunk, sample_buffer, ret); + + long bit_rate = vbr_update_bits * audio_format.sample_rate + / 1152 / 1000; + + cmd = decoder_data(mpd_decoder, is, + chunk, ret * sizeof(chunk[0]), + bit_rate); + } while (cmd != DecoderCommand::STOP); + + mpc_demux_exit(demux); +} + +static float +mpcdec_get_file_duration(struct input_stream *is) +{ + struct mpc_decoder_data data; + data.is = is; + data.decoder = nullptr; + + mpc_reader reader; + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_demux *demux = mpc_demux_init(&reader); + if (demux == nullptr) + return -1; + + mpc_streaminfo info; + mpc_demux_get_info(demux, &info); + mpc_demux_exit(demux); + + return mpc_streaminfo_get_length(&info); +} + +static bool +mpcdec_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + float total_time = mpcdec_get_file_duration(is); + + if (total_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; +} + +static const char *const mpcdec_suffixes[] = { "mpc", nullptr }; + +const struct decoder_plugin mpcdec_decoder_plugin = { + "mpcdec", + nullptr, + nullptr, + mpcdec_decode, + nullptr, + nullptr, + mpcdec_scan_stream, + nullptr, + mpcdec_suffixes, + nullptr, +}; diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/MpcdecDecoderPlugin.hxx new file mode 100644 index 000000000..7e9b51cdb --- /dev/null +++ b/src/decoder/MpcdecDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MPCDEC_HXX +#define MPD_DECODER_MPCDEC_HXX + +extern const struct decoder_plugin mpcdec_decoder_plugin; + +#endif diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx new file mode 100644 index 000000000..928af39e6 --- /dev/null +++ b/src/decoder/Mpg123DecoderPlugin.cxx @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Mpg123DecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <mpg123.h> +#include <stdio.h> + +static constexpr Domain mpg123_domain("mpg123"); + +static bool +mpd_mpg123_init(gcc_unused const config_param ¶m) +{ + 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, + AudioFormat &audio_format) +{ + 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) { + FormatWarning(mpg123_domain, + "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) { + FormatWarning(mpg123_domain, + "mpg123_getformat() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + if (encoding != MPG123_ENC_SIGNED_16) { + /* other formats not yet implemented */ + FormatWarning(mpg123_domain, + "expected MPG123_ENC_SIGNED_16, got %d", + encoding); + return false; + } + + Error error2; + if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16, + channels, error2)) { + LogError(error2); + return false; + } + + return true; +} + +static void +mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) +{ + mpg123_handle *handle; + int error; + off_t num_samples; + struct mpg123_frameinfo info; + + /* open the file */ + + handle = mpg123_new(nullptr, &error); + if (handle == nullptr) { + FormatError(mpg123_domain, + "mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return; + } + + AudioFormat audio_format; + 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, true, + (float)num_samples / + (float)audio_format.sample_rate); + + if (mpg123_info(handle, &info) != MPG123_OK) { + info.vbr = MPG123_CBR; + info.bitrate = 0; + } + + switch (info.vbr) { + case MPG123_ABR: + info.bitrate = info.abr_rate; + break; + case MPG123_CBR: + break; + default: + info.bitrate = 0; + } + + /* the decoder main loop */ + + DecoderCommand cmd; + 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) + FormatWarning(mpg123_domain, + "mpg123_read() failed: %s", + mpg123_plain_strerror(error)); + break; + } + + /* update bitrate for ABR/VBR */ + if (info.vbr != MPG123_CBR) { + /* FIXME: maybe skip, as too expensive? */ + /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ + if (mpg123_info (handle, &info) != MPG123_OK) + info.bitrate = 0; + } + + /* send to MPD */ + + cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate); + + if (cmd == DecoderCommand::SEEK) { + off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; + c = mpg123_seek(handle, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else { + decoder_command_finished(decoder); + decoder_timestamp(decoder, c/(double)audio_format.sample_rate); + } + + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + /* cleanup */ + + mpg123_delete(handle); +} + +static bool +mpd_mpg123_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + mpg123_handle *handle; + int error; + off_t num_samples; + + handle = mpg123_new(nullptr, &error); + if (handle == nullptr) { + FormatError(mpg123_domain, + "mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + AudioFormat audio_format; + if (!mpd_mpg123_open(handle, path_fs, audio_format)) { + mpg123_delete(handle); + return false; + } + + num_samples = mpg123_length(handle); + if (num_samples <= 0) { + mpg123_delete(handle); + return false; + } + + /* ID3 tag support not yet implemented */ + + mpg123_delete(handle); + + tag_handler_invoke_duration(handler, handler_ctx, + num_samples / audio_format.sample_rate); + return true; +} + +static const char *const mpg123_suffixes[] = { + "mp3", + nullptr +}; + +const struct decoder_plugin mpg123_decoder_plugin = { + "mpg123", + mpd_mpg123_init, + mpd_mpg123_finish, + /* streaming not yet implemented */ + nullptr, + mpd_mpg123_file_decode, + mpd_mpg123_scan_file, + nullptr, + nullptr, + mpg123_suffixes, + nullptr, +}; diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/Mpg123DecoderPlugin.hxx new file mode 100644 index 000000000..273b03eaf --- /dev/null +++ b/src/decoder/Mpg123DecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MPG123_HXX +#define MPD_DECODER_MPG123_HXX + +extern const struct decoder_plugin mpg123_decoder_plugin; + +#endif diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx new file mode 100644 index 000000000..d7e5b7642 --- /dev/null +++ b/src/decoder/OggCodec.cxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#include "config.h" +#include "OggCodec.hxx" + +#include <string.h> + +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) +{ + /* oggflac detection based on code in ogg123 and this post + * http://lists.xiph.org/pipermail/flac/2004-December/000393.html + * ogg123 trunk still doesn't have this patch as of June 2005 */ + unsigned char buf[41]; + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); + if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) + return OGG_CODEC_UNKNOWN; + + if ((memcmp(buf + 29, "FLAC", 4) == 0 && + memcmp(buf + 37, "fLaC", 4) == 0) || + memcmp(buf + 28, "FLAC", 4) == 0 || + memcmp(buf + 28, "fLaC", 4) == 0) + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; + + return OGG_CODEC_VORBIS; +} diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx new file mode 100644 index 000000000..eb709286b --- /dev/null +++ b/src/decoder/OggCodec.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#ifndef MPD_OGG_CODEC_HXX +#define MPD_OGG_CODEC_HXX + +#include "DecoderAPI.hxx" + +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; + +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); + +#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx new file mode 100644 index 000000000..9df4c6455 --- /dev/null +++ b/src/decoder/OggFind.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OggFind.hxx" +#include "OggSyncState.hxx" + +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) +{ + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r == 0) { + if (!oy.ExpectPageIn(os)) + return false; + + continue; + } else if (r > 0 && packet.e_o_s) + return true; + } +} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx new file mode 100644 index 000000000..7d18d2067 --- /dev/null +++ b/src/decoder/OggFind.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OGG_FIND_HXX +#define MPD_OGG_FIND_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +class OggSyncState; + +/** + * Skip all pages/packets until an end-of-stream (EOS) packet for the + * specified stream is found. + * + * @return true if the EOS packet was found + */ +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); + +#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx new file mode 100644 index 000000000..eaeb9bd8c --- /dev/null +++ b/src/decoder/OggSyncState.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OGG_SYNC_STATE_HXX +#define MPD_OGG_SYNC_STATE_HXX + +#include "check.h" +#include "OggUtil.hxx" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Wrapper for an ogg_sync_state. + */ +class OggSyncState { + ogg_sync_state oy; + + input_stream &is; + struct decoder *const decoder; + +public: + OggSyncState(input_stream &_is, struct decoder *const _decoder=nullptr) + :is(_is), decoder(_decoder) { + ogg_sync_init(&oy); + } + + ~OggSyncState() { + ogg_sync_clear(&oy); + } + + void Reset() { + ogg_sync_reset(&oy); + } + + bool Feed(size_t size) { + return OggFeed(oy, decoder, &is, size); + } + + bool ExpectPage(ogg_page &page) { + return OggExpectPage(oy, page, decoder, &is); + } + + bool ExpectFirstPage(ogg_stream_state &os) { + return OggExpectFirstPage(oy, os, decoder, &is); + } + + bool ExpectPageIn(ogg_stream_state &os) { + return OggExpectPageIn(oy, os, decoder, &is); + } + + bool ExpectPageSeek(ogg_page &page) { + return OggExpectPageSeek(oy, page, decoder, &is); + } + + bool ExpectPageSeekIn(ogg_stream_state &os) { + return OggExpectPageSeekIn(oy, os, decoder, &is); + } +}; + +#endif diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 000000000..0e2f48f51 --- /dev/null +++ b/src/decoder/OggUtil.cxx @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OggUtil.hxx" +#include "DecoderAPI.hxx" + +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + input_stream *input_stream, size_t size) +{ + char *buffer = ogg_sync_buffer(&oy, size); + if (buffer == nullptr) + return false; + + size_t nbytes = decoder_read(decoder, input_stream, + buffer, size); + if (nbytes == 0) + return false; + + ogg_sync_wrote(&oy, nbytes); + return true; +} + +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream) +{ + while (true) { + int r = ogg_sync_pageout(&oy, &page); + if (r != 0) + return r > 0; + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream) +{ + size_t remaining_skipped = 16384; + + while (true) { + int r = ogg_sync_pageseek(&oy, &page); + if (r > 0) + return true; + + if (r < 0) { + /* skipped -r bytes */ + size_t nbytes = -r; + if (nbytes > remaining_skipped) + /* still no ogg page - we lost our + patience, abort */ + return false; + + remaining_skipped -= nbytes; + continue; + } + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is) +{ + ogg_page page; + if (!OggExpectPageSeek(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx new file mode 100644 index 000000000..324797815 --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +struct input_stream; +struct decoder; + +/** + * Feed data from the #input_stream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, input_stream *is, + size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPage(), ogg_stream_init() and + * ogg_stream_pagein(). + * + * @return true if the stream was initialized and the first page was + * delivered to it + */ +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Combines OggExpectPage() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + decoder *decoder, input_stream *input_stream); + +/** + * Combines OggExpectPageSeek() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + decoder *decoder, input_stream *is); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..96c52a083 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "OpusDecoderPlugin.h" +#include "OpusDomain.hxx" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggUtil.hxx" +#include "OggFind.hxx" +#include "OggSyncState.hxx" +#include "DecoderAPI.hxx" +#include "OggCodec.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <stdio.h> +#include <string.h> + +static const opus_int32 opus_sample_rate = 48000; + +gcc_pure +static bool +IsOpusHead(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; +} + +gcc_pure +static bool +IsOpusTags(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; +} + +static bool +mpd_opus_init(gcc_unused const config_param ¶m) +{ + LogDebug(opus_domain, opus_get_version_string()); + + return true; +} + +class MPDOpusDecoder { + struct decoder *decoder; + struct input_stream *input_stream; + + ogg_stream_state os; + + OpusDecoder *opus_decoder; + opus_int16 *output_buffer; + unsigned output_size; + + bool os_initialized; + bool found_opus; + + int opus_serialno; + + size_t frame_size; + +public: + MPDOpusDecoder(struct decoder *_decoder, + struct input_stream *_input_stream) + :decoder(_decoder), input_stream(_input_stream), + opus_decoder(nullptr), + output_buffer(nullptr), output_size(0), + os_initialized(false), found_opus(false) {} + ~MPDOpusDecoder(); + + bool ReadFirstPage(OggSyncState &oy); + bool ReadNextPage(OggSyncState &oy); + + DecoderCommand HandlePackets(); + DecoderCommand HandlePacket(const ogg_packet &packet); + DecoderCommand HandleBOS(const ogg_packet &packet); + DecoderCommand HandleTags(const ogg_packet &packet); + DecoderCommand HandleAudio(const ogg_packet &packet); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + g_free(output_buffer); + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +inline bool +MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) +{ + assert(!os_initialized); + + if (!oy.ExpectFirstPage(os)) + return false; + + os_initialized = true; + return true; +} + +inline bool +MPDOpusDecoder::ReadNextPage(OggSyncState &oy) +{ + assert(os_initialized); + + ogg_page page; + if (!oy.ExpectPage(page)) + return false; + + const auto page_serialno = ogg_page_serialno(&page); + if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + return true; +} + +inline DecoderCommand +MPDOpusDecoder::HandlePackets() +{ + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + auto cmd = HandlePacket(packet); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +inline DecoderCommand +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return DecoderCommand::STOP; + + if (packet.b_o_s) + return HandleBOS(packet); + else if (!found_opus) + return DecoderCommand::STOP; + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +inline DecoderCommand +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (found_opus || !IsOpusHead(packet)) + return DecoderCommand::STOP; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) + return DecoderCommand::STOP; + + assert(opus_decoder == nullptr); + assert(output_buffer == nullptr); + + opus_serialno = os.serialno; + found_opus = true; + + /* TODO: parse attributes from the OpusHead (sample rate, + channels, ...) */ + + int opus_error; + opus_decoder = opus_decoder_create(opus_sample_rate, channels, + &opus_error); + if (opus_decoder == nullptr) { + FormatError(opus_domain, "libopus error: %s", + opus_strerror(opus_error)); + return DecoderCommand::STOP; + } + + const AudioFormat audio_format(opus_sample_rate, + SampleFormat::S16, channels); + decoder_initialized(decoder, audio_format, false, -1); + frame_size = audio_format.GetFrameSize(); + + /* allocate an output buffer for 16 bit PCM samples big enough + to hold a quarter second, larger than 120ms required by + libopus */ + output_size = audio_format.sample_rate / 4; + output_buffer = (opus_int16 *) + g_malloc(sizeof(*output_buffer) * output_size * + audio_format.channels); + + return decoder_get_command(decoder); +} + +inline DecoderCommand +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + TagBuilder tag_builder; + + DecoderCommand cmd; + if (ScanOpusTags(packet.packet, packet.bytes, + &add_tag_handler, &tag_builder) && + !tag_builder.IsEmpty()) { + Tag tag; + tag_builder.Commit(tag); + cmd = decoder_tag(decoder, input_stream, std::move(tag)); + } else + cmd = decoder_get_command(decoder); + + return cmd; +} + +inline DecoderCommand +MPDOpusDecoder::HandleAudio(const ogg_packet &packet) +{ + assert(opus_decoder != nullptr); + + int nframes = opus_decode(opus_decoder, + (const unsigned char*)packet.packet, + packet.bytes, + output_buffer, output_size, + 0); + if (nframes < 0) { + LogError(opus_domain, opus_strerror(nframes)); + return DecoderCommand::STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + auto cmd = decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +static void +mpd_opus_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream->LockSeek(0, SEEK_SET, IgnoreError()); + + MPDOpusDecoder d(decoder, input_stream); + OggSyncState oy(*input_stream, decoder); + + if (!d.ReadFirstPage(oy)) + return; + + while (true) { + auto cmd = d.HandlePackets(); + if (cmd != DecoderCommand::NONE) + break; + + if (!d.ReadNextPage(oy)) + break; + + } +} + +static bool +SeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + input_stream *is) +{ + if (is->size > 0 && is->size - is->offset < 65536) + return OggFindEOS(oy, os, packet); + + if (!is->CheapSeeking()) + return false; + + oy.Reset(); + + Error error; + return is->LockSeek(-65536, SEEK_END, error) && + oy.ExpectPageSeekIn(os) && + OggFindEOS(oy, os, packet); +} + +static bool +mpd_opus_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + OggSyncState oy(*is); + + ogg_stream_state os; + if (!oy.ExpectFirstPage(os)) + return false; + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_packet packet; + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r < 0) { + result = false; + break; + } + + if (r == 0) { + if (remaining_pages-- == 0) + break; + + if (!oy.ExpectPageIn(os)) { + result = false; + break; + } + + continue; + } + + if (packet.b_o_s) { + if (!IsOpusHead(packet)) + break; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + result = false; + break; + } + + result = true; + } else if (!result) + break; + else if (IsOpusTags(packet)) { + if (!ScanOpusTags(packet.packet, packet.bytes, + handler, handler_ctx)) + result = false; + + break; + } + } + + if (packet.e_o_s || SeekFindEOS(oy, os, packet, is)) + tag_handler_invoke_duration(handler, handler_ctx, + packet.granulepos / opus_sample_rate); + + ogg_stream_clear(&os); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + "audio/opus", + nullptr +}; + +const struct decoder_plugin opus_decoder_plugin = { + "opus", + mpd_opus_init, + nullptr, + mpd_opus_stream_decode, + nullptr, + nullptr, + mpd_opus_scan_stream, + nullptr, + opus_suffixes, + opus_mime_types, +}; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h new file mode 100644 index 000000000..c95d6ded3 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct decoder_plugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/OpusDomain.cxx new file mode 100644 index 000000000..2b8bb1bba --- /dev/null +++ b/src/decoder/OpusDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusDomain.hxx" +#include "util/Domain.hxx" + +const Domain opus_domain("opus"); diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/OpusDomain.hxx new file mode 100644 index 000000000..488eca27d --- /dev/null +++ b/src/decoder/OpusDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_DOMAIN_HXX +#define MPD_OPUS_DOMAIN_HXX + +#include "check.h" + +extern const class Domain opus_domain; + +#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx new file mode 100644 index 000000000..c57e08e10 --- /dev/null +++ b/src/decoder/OpusHead.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusHead.hxx" + +#include <stdint.h> +#include <string.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx new file mode 100644 index 000000000..9f75c4f70 --- /dev/null +++ b/src/decoder/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx new file mode 100644 index 000000000..7e161fd0f --- /dev/null +++ b/src/decoder/OpusReader.hxx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + char *dest = new char[length + 1]; + memcpy(dest, src, length); + dest[length] = 0; + return dest; + } +}; + +#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx new file mode 100644 index 000000000..f09d79c3b --- /dev/null +++ b/src/decoder/OpusTags.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OpusTags.hxx" +#include "OpusReader.hxx" +#include "XiphTags.hxx" +#include "tag/TagHandler.hxx" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +static void +ScanOneOpusTag(const char *name, const char *value, + const struct tag_handler *handler, void *ctx) +{ + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + enum tag_type t = tag_table_lookup_i(xiph_tags, name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx) +{ + OpusReader r(data, size); + if (!r.Expect("OpusTags", 8)) + return false; + + if (handler->pair == nullptr && handler->tag == nullptr) + return true; + + if (!r.SkipString()) + return false; + + uint32_t n; + if (!r.ReadWord(n)) + return false; + + while (n-- > 0) { + char *p = r.ReadString(); + if (p == nullptr) + return false; + + char *eq = strchr(p, '='); + if (eq != nullptr && eq > p) { + *eq = 0; + + ScanOneOpusTag(p, eq + 1, handler, ctx); + } + + free(p); + } + + return true; +} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx new file mode 100644 index 000000000..2f3eec844 --- /dev/null +++ b/src/decoder/OpusTags.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx new file mode 100644 index 000000000..6996b583a --- /dev/null +++ b/src/decoder/PcmDecoderPlugin.cxx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/PcmDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +extern "C" { +#include "util/byte_reverse.h" +} + +#include <glib.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> /* for SEEK_SET */ + +static void +pcm_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + static constexpr AudioFormat audio_format = { + 44100, + SampleFormat::S16, + 2, + }; + + const char *const mime = is->GetMimeType(); + const bool reverse_endian = mime != nullptr && + strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; + + const double time_to_size = audio_format.GetTimeToSize(); + + float total_time = -1; + const goffset size = is->GetSize(); + if (size >= 0) + total_time = size / time_to_size; + + decoder_initialized(decoder, audio_format, + is->IsSeekable(), total_time); + + DecoderCommand cmd; + do { + char buffer[4096]; + + size_t nbytes = decoder_read(decoder, is, + buffer, sizeof(buffer)); + + if (nbytes == 0 && is->LockIsEOF()) + break; + + if (reverse_endian) + /* make sure we deliver samples in host byte order */ + reverse_bytes_16((uint16_t *)buffer, + (uint16_t *)buffer, + (uint16_t *)(buffer + nbytes)); + + cmd = nbytes > 0 + ? decoder_data(decoder, is, + buffer, nbytes, 0) + : decoder_get_command(decoder); + if (cmd == DecoderCommand::SEEK) { + goffset offset = (goffset)(time_to_size * + decoder_seek_where(decoder)); + + Error error; + if (is->LockSeek(offset, SEEK_SET, error)) { + decoder_command_finished(decoder); + } else { + LogError(error); + decoder_seek_error(decoder); + } + + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); +} + +static const char *const pcm_mime_types[] = { + /* for streams obtained by the cdio_paranoia input plugin */ + "audio/x-mpd-cdda-pcm", + + /* same as above, but with reverse byte order */ + "audio/x-mpd-cdda-pcm-reverse", + + nullptr +}; + +const struct decoder_plugin pcm_decoder_plugin = { + "pcm", + nullptr, + nullptr, + pcm_stream_decode, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + pcm_mime_types, +}; diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/PcmDecoderPlugin.hxx new file mode 100644 index 000000000..2883e866e --- /dev/null +++ b/src/decoder/PcmDecoderPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Not really a decoder; this plugin forwards its input data "as-is". + * + * It was written only to support the "cdio_paranoia" input plugin, + * which does not need a decoder. + */ + +#ifndef MPD_DECODER_PCM_HXX +#define MPD_DECODER_PCM_HXX + +extern const struct decoder_plugin pcm_decoder_plugin; + +#endif diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx new file mode 100644 index 000000000..5c7efe230 --- /dev/null +++ b/src/decoder/SndfileDecoderPlugin.cxx @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SndfileDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <sndfile.h> + +static constexpr Domain sndfile_domain("sndfile"); + +static sf_count_t +sndfile_vio_get_filelen(void *user_data) +{ + const struct input_stream *is = (const struct input_stream *)user_data; + + return is->GetSize(); +} + +static sf_count_t +sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) +{ + struct input_stream *is = (struct input_stream *)user_data; + + if (!is->LockSeek(offset, whence, IgnoreError())) + return -1; + + return is->GetOffset(); +} + +static sf_count_t +sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) +{ + struct input_stream *is = (struct input_stream *)user_data; + + Error error; + size_t nbytes = is->LockRead(ptr, count, error); + if (nbytes == 0 && error.IsDefined()) { + LogError(error); + return -1; + } + + return nbytes; +} + +static sf_count_t +sndfile_vio_write(gcc_unused const void *ptr, + gcc_unused sf_count_t count, + gcc_unused void *user_data) +{ + /* no writing! */ + return -1; +} + +static sf_count_t +sndfile_vio_tell(void *user_data) +{ + const struct input_stream *is = (const struct input_stream *)user_data; + + return is->GetOffset(); +} + +/** + * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a + * libsndfile stream. + */ +static SF_VIRTUAL_IO vio = { + sndfile_vio_get_filelen, + sndfile_vio_seek, + sndfile_vio_read, + sndfile_vio_write, + sndfile_vio_tell, +}; + +/** + * Converts a frame number to a timestamp (in seconds). + */ +static float +frame_to_time(sf_count_t frame, const AudioFormat *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 AudioFormat *audio_format) +{ + return (sf_count_t)(t * audio_format->sample_rate); +} + +static void +sndfile_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + SNDFILE *sf; + SF_INFO info; + size_t frame_size; + sf_count_t read_frames, num_frames; + int buffer[4096]; + + info.format = 0; + + sf = sf_open_virtual(&vio, SFM_READ, &info, is); + if (sf == nullptr) { + LogWarning(sndfile_domain, "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. */ + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, info.samplerate, + SampleFormat::S32, + info.channels, error)) { + LogError(error); + return; + } + + decoder_initialized(decoder, audio_format, info.seekable, + frame_to_time(info.frames, &audio_format)); + + frame_size = audio_format.GetFrameSize(); + read_frames = sizeof(buffer) / frame_size; + + DecoderCommand cmd; + 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 == DecoderCommand::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 = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + sf_close(sf); +} + +static bool +sndfile_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + SNDFILE *sf; + SF_INFO info; + const char *p; + + info.format = 0; + + sf = sf_open(path_fs, SFM_READ, &info); + if (sf == nullptr) + return false; + + if (!audio_valid_sample_rate(info.samplerate)) { + sf_close(sf); + FormatWarning(sndfile_domain, + "Invalid sample rate in %s", path_fs); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, + info.frames / info.samplerate); + + p = sf_get_string(sf, SF_STR_TITLE); + if (p != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, p); + + p = sf_get_string(sf, SF_STR_ARTIST); + if (p != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ARTIST, p); + + p = sf_get_string(sf, SF_STR_DATE); + if (p != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_DATE, p); + + sf_close(sf); + + return true; +} + +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 */ + + nullptr +}; + +static const char *const sndfile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + + /* what are the MIME types of the other supported formats? */ + + nullptr +}; + +const struct decoder_plugin sndfile_decoder_plugin = { + "sndfile", + nullptr, + nullptr, + sndfile_stream_decode, + nullptr, + sndfile_scan_file, + nullptr, + nullptr, + sndfile_suffixes, + sndfile_mime_types, +}; diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/SndfileDecoderPlugin.hxx new file mode 100644 index 000000000..ba60fafd0 --- /dev/null +++ b/src/decoder/SndfileDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SNDFILE_HXX +#define MPD_DECODER_SNDFILE_HXX + +extern const struct decoder_plugin sndfile_decoder_plugin; + +#endif diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx new file mode 100644 index 000000000..402ee7c2b --- /dev/null +++ b/src/decoder/VorbisComments.cxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "VorbisComments.hxx" +#include "XiphTags.hxx" +#include "tag/Tag.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "ReplayGainInfo.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +static const char * +vorbis_comment_value(const char *comment, const char *needle) +{ + size_t len = strlen(needle); + + if (g_ascii_strncasecmp(comment, needle, len) == 0 && + comment[len] == '=') + return comment + len + 1; + + return NULL; +} + +bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) +{ + const char *temp; + bool found = false; + + replay_gain_info_init(rgi); + + while (*comments) { + if ((temp = + vorbis_comment_value(*comments, "replaygain_track_gain"))) { + rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_gain"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_track_peak"))) { + rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_peak"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); + found = true; + } + + comments++; + } + + return found; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +vorbis_copy_comment(const char *comment, + const char *name, enum tag_type tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != NULL) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static void +vorbis_scan_comment(const char *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != NULL) { + char *name = g_strdup((const char*)comment); + char *value = strchr(name, '='); + + if (value != NULL && value > name) { + *value++ = 0; + tag_handler_invoke_pair(handler, handler_ctx, + name, value); + } + + g_free(name); + } + + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) + if (vorbis_copy_comment(comment, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(comment, + tag_item_names[i], tag_type(i), + handler, handler_ctx)) + return; +} + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx) +{ + while (*comments) + vorbis_scan_comment(*comments++, + handler, handler_ctx); + +} + +Tag * +vorbis_comments_to_tag(char **comments) +{ + TagBuilder tag_builder; + vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); + return tag_builder.IsEmpty() + ? nullptr + : tag_builder.Commit(); +} diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx new file mode 100644 index 000000000..7a8374785 --- /dev/null +++ b/src/decoder/VorbisComments.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_VORBIS_COMMENTS_HXX +#define MPD_VORBIS_COMMENTS_HXX + +#include "check.h" + +struct replay_gain_info; +struct tag_handler; +struct Tag; + +bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments); + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx); + +Tag * +vorbis_comments_to_tag(char **comments); + +#endif diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx new file mode 100644 index 000000000..55ce943e8 --- /dev/null +++ b/src/decoder/VorbisDecoderPlugin.cxx @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisDecoderPlugin.h" +#include "VorbisComments.hxx" +#include "VorbisDomain.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "OggCodec.hxx" +#include "util/Error.hxx" +#include "util/UriUtil.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +#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. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include <glib.h> + +#include <assert.h> +#include <errno.h> +#include <unistd.h> + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define VORBIS_BIG_ENDIAN true +#else +#define VORBIS_BIG_ENDIAN false +#endif + +struct vorbis_input_stream { + struct decoder *decoder; + + struct input_stream *input_stream; + bool seekable; +}; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) +{ + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); + + errno = 0; + + return ret / size; +} + +static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) +{ + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + + Error error; + return vis->seekable && + (!vis->decoder || decoder_get_command(vis->decoder) != DecoderCommand::STOP) && + vis->input_stream->LockSeek(offset, whence, error) + ? 0 : -1; +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(gcc_unused void *data) +{ + return 0; +} + +static long ogg_tell_cb(void *data) +{ + struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + + return (long)vis->input_stream->offset; +} + +static const ov_callbacks vorbis_is_callbacks = { + ogg_read_cb, + ogg_seek_cb, + ogg_close_cb, + ogg_tell_cb, +}; + +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->CheapSeeking(); + + int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); + if (ret < 0) { + if (decoder == NULL || + decoder_get_command(decoder) == DecoderCommand::NONE) + FormatWarning(vorbis_domain, + "Failed to open Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return false; + } + + return true; +} + +static void +vorbis_send_comments(struct decoder *decoder, struct input_stream *is, + char **comments) +{ + Tag *tag = vorbis_comments_to_tag(comments); + if (!tag) + return; + + decoder_tag(decoder, is, std::move(*tag)); + delete tag; +} + +#ifndef HAVE_TREMOR +static void +vorbis_interleave(float *dest, const float *const*src, + unsigned nframes, unsigned channels) +{ + for (const float *const*src_end = src + channels; + src != src_end; ++src, ++dest) { + float *d = dest; + for (const float *s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + +/* public */ +static void +vorbis_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream->LockSeek(0, SEEK_SET, IgnoreError()); + + struct vorbis_input_stream vis; + OggVorbis_File vf; + if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) + return; + + const vorbis_info *vi = ov_info(&vf, -1); + if (vi == NULL) { + LogWarning(vorbis_domain, "ov_info() has failed"); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, vi->rate, +#ifdef HAVE_TREMOR + SampleFormat::S16, +#else + SampleFormat::FLOAT, +#endif + vi->channels, error)) { + LogError(error); + return; + } + + float total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, audio_format, vis.seekable, total_time); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + G_N_ELEMENTS(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + + DecoderCommand cmd = decoder_get_command(decoder); + do { + if (cmd == DecoderCommand::SEEK) { + double seek_where = decoder_seek_where(decoder); + if (0 == ov_time_seek_page(&vf, seek_where)) { + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + VORBIS_BIG_ENDIAN, 2, 1, + ¤t_section); +#else + float **per_channel; + long nframes = ov_read_float(&vf, &per_channel, + frames_per_buffer, + ¤t_section); + long nbytes = nframes; + if (nframes > 0) { + vorbis_interleave(buffer, + (const float*const*)per_channel, + nframes, audio_format.channels); + nbytes *= frame_size; + } +#endif + + if (nbytes == OV_HOLE) /* bad packet */ + nbytes = 0; + else if (nbytes <= 0) + /* break on EOF or other error */ + break; + + if (current_section != prev_section) { + vi = ov_info(&vf, -1); + if (vi == NULL) { + LogWarning(vorbis_domain, + "ov_info() has failed"); + break; + } + + if (vi->rate != (long)audio_format.sample_rate || + vi->channels != (int)audio_format.channels) { + /* we don't support audio format + change yet */ + LogWarning(vorbis_domain, + "audio format change, stopping here"); + break; + } + + char **comments = ov_comment(&vf, -1)->user_comments; + vorbis_send_comments(decoder, input_stream, comments); + + struct replay_gain_info rgi; + if (vorbis_comments_to_replay_gain(&rgi, comments)) + decoder_replay_gain(decoder, &rgi); + + prev_section = current_section; + } + + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; + + cmd = decoder_data(decoder, input_stream, + buffer, nbytes, + kbit_rate); + } while (cmd != DecoderCommand::STOP); + + ov_clear(&vf); +} + +static bool +vorbis_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + struct vorbis_input_stream vis; + OggVorbis_File vf; + + if (!vorbis_is_open(&vis, &vf, NULL, is)) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + (int)(ov_time_total(&vf, -1) + 0.5)); + + vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, + handler, handler_ctx); + + ov_clear(&vf); + return true; +} + +static const char *const vorbis_suffixes[] = { + "ogg", "oga", NULL +}; + +static const char *const vorbis_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + NULL +}; + +const struct decoder_plugin vorbis_decoder_plugin = { + "vorbis", + nullptr, + nullptr, + vorbis_stream_decode, + nullptr, + nullptr, + vorbis_scan_stream, + nullptr, + vorbis_suffixes, + vorbis_mime_types +}; diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h new file mode 100644 index 000000000..618c9ffde --- /dev/null +++ b/src/decoder/VorbisDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_VORBIS_H +#define MPD_DECODER_VORBIS_H + +extern const struct decoder_plugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/VorbisDomain.cxx new file mode 100644 index 000000000..d7c70a641 --- /dev/null +++ b/src/decoder/VorbisDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisDomain.hxx" +#include "util/Domain.hxx" + +const Domain vorbis_domain("vorbis"); diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx new file mode 100644 index 000000000..69e2e11cb --- /dev/null +++ b/src/decoder/VorbisDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_VORBIS_DOMAIN_HXX +#define MPD_VORBIS_DOMAIN_HXX + +#include "check.h" + +extern const class Domain vorbis_domain; + +#endif diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx new file mode 100644 index 000000000..8ee898e30 --- /dev/null +++ b/src/decoder/WavpackDecoderPlugin.cxx @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "WavpackDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/ApeTag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <wavpack/wavpack.h> +#include <glib.h> + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#define ERRORLEN 80 + +static constexpr Domain wavpack_domain("wavpack"); + +/** A pointer type for format converter function. */ +typedef void (*format_samples_t)( + int bytes_per_sample, + void *buffer, uint32_t count +); + +/* + * This function has been borrowed from the tiny player found on + * wavpack.com. Modifications were required because mpd only handles + * max 24-bit samples. + */ +static void +format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) +{ + int32_t *src = (int32_t *)buffer; + + switch (bytes_per_sample) { + case 1: { + int8_t *dst = (int8_t *)buffer; + /* + * The asserts like the following one are because we do the + * formatting of samples within a single buffer. The size + * of the output samples never can be greater than the size + * of the input ones. Otherwise we would have an overflow. + */ + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 8-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + case 2: { + uint16_t *dst = (uint16_t *)buffer; + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 16-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + + case 3: + case 4: + /* do nothing */ + break; + } +} + +/* + * This function converts floating point sample data to 24-bit integer. + */ +static void +format_samples_float(gcc_unused int bytes_per_sample, void *buffer, + uint32_t count) +{ + float *p = (float *)buffer; + + while (count--) { + *p /= (1 << 23); + ++p; + } +} + +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static SampleFormat +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SampleFormat::FLOAT; + + switch (bytes_per_sample) { + case 1: + return SampleFormat::S8; + + case 2: + return SampleFormat::S16; + + case 3: + return SampleFormat::S24_P32; + + case 4: + return SampleFormat::S32; + + default: + return SampleFormat::UNDEFINED; + } +} + +/* + * This does the main decoding thing. + * Requires an already opened WavpackContext. + */ +static void +wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) +{ + bool is_float; + SampleFormat sample_format; + AudioFormat audio_format; + format_samples_t format_samples; + float total_time; + int bytes_per_sample, output_sample_size; + + is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + Error error; + if (!audio_format_init_checked(audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), error)) { + LogError(error); + return; + } + + if (is_float) { + format_samples = format_samples_float; + } else { + format_samples = format_samples_int; + } + + total_time = WavpackGetNumSamples(wpc); + total_time /= audio_format.sample_rate; + bytes_per_sample = WavpackGetBytesPerSample(wpc); + output_sample_size = audio_format.GetFrameSize(); + + /* wavpack gives us all kind of samples in a 32-bit space */ + int32_t chunk[1024]; + const uint32_t samples_requested = G_N_ELEMENTS(chunk) / + audio_format.channels; + + decoder_initialized(decoder, audio_format, can_seek, total_time); + + DecoderCommand cmd = decoder_get_command(decoder); + while (cmd != DecoderCommand::STOP) { + if (cmd == DecoderCommand::SEEK) { + if (can_seek) { + unsigned where = decoder_seek_where(decoder) * + audio_format.sample_rate; + + if (WavpackSeekSample(wpc, where)) { + decoder_command_finished(decoder); + } else { + decoder_seek_error(decoder); + } + } else { + decoder_seek_error(decoder); + } + } + + uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, + samples_requested); + if (samples_got == 0) + break; + + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + + 0.5); + format_samples(bytes_per_sample, chunk, + samples_got * audio_format.channels); + + cmd = decoder_data(decoder, NULL, chunk, + samples_got * output_sample_size, + bitrate); + } +} + +/** + * Locate and parse a floating point tag. Returns true if it was + * found. + */ +static bool +wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) +{ + char buffer[64]; + int ret; + + ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)); + if (ret <= 0) + return false; + + *value_r = atof(buffer); + return true; +} + +static bool +wavpack_replaygain(struct replay_gain_info *replay_gain_info, + WavpackContext *wpc) +{ + bool found = false; + + replay_gain_info_init(replay_gain_info); + + found |= wavpack_tag_float( + wpc, "replaygain_track_gain", + &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain + ); + found |= wavpack_tag_float( + wpc, "replaygain_track_peak", + &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak + ); + found |= wavpack_tag_float( + wpc, "replaygain_album_gain", + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain + ); + found |= wavpack_tag_float( + wpc, "replaygain_album_peak", + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak + ); + + return found; +} + +static void +wavpack_scan_tag_item(WavpackContext *wpc, const char *name, + enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[1024]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_tag(handler, handler_ctx, type, buffer); + +} + +static void +wavpack_scan_pair(WavpackContext *wpc, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[8192]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_pair(handler, handler_ctx, name, buffer); +} + +/* + * Reads metainfo from the specified file. + */ +static bool +wavpack_scan_file(const char *fname, + const struct tag_handler *handler, void *handler_ctx) +{ + WavpackContext *wpc; + char error[ERRORLEN]; + + wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); + if (wpc == NULL) { + FormatError(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + fname, error); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, + WavpackGetNumSamples(wpc) / + WavpackGetSampleRate(wpc)); + + /* the WavPack format implies APEv2 tags, which means we can + reuse the mapping from tag_ape.c */ + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + const char *name = tag_item_names[i]; + if (name != NULL) + wavpack_scan_tag_item(wpc, name, (enum tag_type)i, + handler, handler_ctx); + } + + for (const struct tag_table *i = ape_tags; i->name != NULL; ++i) + wavpack_scan_tag_item(wpc, i->name, i->type, + handler, handler_ctx); + + if (handler->pair != NULL) { + char name[64]; + + for (int i = 0, n = WavpackGetNumTagItems(wpc); + i < n; ++i) { + int len = WavpackGetTagItemIndexed(wpc, i, name, + sizeof(name)); + if (len <= 0 || (unsigned)len >= sizeof(name)) + continue; + + wavpack_scan_pair(wpc, name, handler, handler_ctx); + } + } + + WavpackCloseFile(wpc); + + return true; +} + +/* + * mpd input_stream <=> WavpackStreamReader wrapper callbacks + */ + +/* This struct is needed for per-stream last_byte storage. */ +struct wavpack_input { + struct decoder *decoder; + struct input_stream *is; + /* Needed for push_back_byte() */ + int last_byte; +}; + +/** + * Little wrapper for struct wavpack_input to cast from void *. + */ +static struct wavpack_input * +wpin(void *id) +{ + assert(id); + return (struct wavpack_input *)id; +} + +static int32_t +wavpack_input_read_bytes(void *id, void *data, int32_t bcount) +{ + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (wpin(id)->last_byte != EOF) { + *buf++ = wpin(id)->last_byte; + wpin(id)->last_byte = EOF; + --bcount; + ++i; + } + + /* wavpack fails if we return a partial read, so we just wait + until the buffer is full */ + while (bcount > 0) { + size_t nbytes = decoder_read( + wpin(id)->decoder, wpin(id)->is, buf, bcount + ); + if (nbytes == 0) { + /* EOF, error or a decoder command */ + break; + } + + i += nbytes; + bcount -= nbytes; + buf += nbytes; + } + + return i; +} + +static uint32_t +wavpack_input_get_pos(void *id) +{ + return wpin(id)->is->offset; +} + +static int +wavpack_input_set_pos_abs(void *id, uint32_t pos) +{ + return wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1; +} + +static int +wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) +{ + return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 0 : -1; +} + +static int +wavpack_input_push_back_byte(void *id, int c) +{ + if (wpin(id)->last_byte == EOF) { + wpin(id)->last_byte = c; + return c; + } else { + return EOF; + } +} + +static uint32_t +wavpack_input_get_length(void *id) +{ + if (wpin(id)->is->size < 0) + return 0; + + return wpin(id)->is->size; +} + +static int +wavpack_input_can_seek(void *id) +{ + return wpin(id)->is->seekable; +} + +static WavpackStreamReader mpd_is_reader = { + wavpack_input_read_bytes, + wavpack_input_get_pos, + wavpack_input_set_pos_abs, + wavpack_input_set_pos_rel, + wavpack_input_push_back_byte, + wavpack_input_get_length, + wavpack_input_can_seek, + nullptr /* no need to write edited tags */ +}; + +static void +wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, + struct input_stream *is) +{ + isp->decoder = decoder; + isp->is = is; + isp->last_byte = EOF; +} + +static struct input_stream * +wavpack_open_wvc(struct decoder *decoder, const char *uri, + Mutex &mutex, Cond &cond, + struct wavpack_input *wpi) +{ + struct input_stream *is_wvc; + char *wvc_url = NULL; + char first_byte; + size_t nbytes; + + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + if (uri == NULL) + return nullptr; + + wvc_url = g_strconcat(uri, "c", NULL); + + is_wvc = input_stream::Open(wvc_url, mutex, cond, IgnoreError()); + g_free(wvc_url); + + if (is_wvc == NULL) + return NULL; + + /* + * And we try to buffer in order to get know + * about a possible 404 error. + */ + nbytes = decoder_read( + decoder, is_wvc, &first_byte, sizeof(first_byte) + ); + if (nbytes == 0) { + is_wvc->Close(); + return NULL; + } + + /* push it back */ + wavpack_input_init(wpi, decoder, is_wvc); + wpi->last_byte = first_byte; + return is_wvc; +} + +/* + * Decodes a stream. + */ +static void +wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + struct input_stream *is_wvc; + int open_flags = OPEN_NORMALIZE; + struct wavpack_input isp, isp_wvc; + bool can_seek = is->seekable; + + is_wvc = wavpack_open_wvc(decoder, is->uri.c_str(), + is->mutex, is->cond, + &isp_wvc); + if (is_wvc != NULL) { + open_flags |= OPEN_WVC; + can_seek &= is_wvc->seekable; + } + + if (!can_seek) { + open_flags |= OPEN_STREAMING; + } + + wavpack_input_init(&isp, decoder, is); + wpc = WavpackOpenFileInputEx( + &mpd_is_reader, &isp, + open_flags & OPEN_WVC ? &isp_wvc : NULL, + error, open_flags, 23 + ); + + if (wpc == NULL) { + FormatError(wavpack_domain, + "failed to open WavPack stream: %s", error); + return; + } + + wavpack_decode(decoder, wpc, can_seek); + + WavpackCloseFile(wpc); + if (open_flags & OPEN_WVC) { + is_wvc->Close(); + } +} + +/* + * Decodes a file. + */ +static void +wavpack_filedecode(struct decoder *decoder, const char *fname) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + + wpc = WavpackOpenFileInput( + fname, error, + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 + ); + if (wpc == NULL) { + FormatWarning(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + fname, error); + return; + } + + 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); + + WavpackCloseFile(wpc); +} + +static char const *const wavpack_suffixes[] = { + "wv", + NULL +}; + +static char const *const wavpack_mime_types[] = { + "audio/x-wavpack", + NULL +}; + +const struct decoder_plugin wavpack_decoder_plugin = { + "wavpack", + nullptr, + nullptr, + wavpack_streamdecode, + wavpack_filedecode, + wavpack_scan_file, + nullptr, + nullptr, + wavpack_suffixes, + wavpack_mime_types +}; diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx new file mode 100644 index 000000000..9ebe6354f --- /dev/null +++ b/src/decoder/WavpackDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_WAVPACK_HXX +#define MPD_DECODER_WAVPACK_HXX + +extern const struct decoder_plugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx new file mode 100644 index 000000000..1a390706f --- /dev/null +++ b/src/decoder/WildmidiDecoderPlugin.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "WildmidiDecoderPlugin.hxx" +#include "DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +extern "C" { +#include <wildmidi_lib.h> +} + +static constexpr Domain wildmidi_domain("wildmidi"); + +static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000; + +static bool +wildmidi_init(const config_param ¶m) +{ + Error error; + const Path path = param.GetBlockPath("config_file", + "/etc/timidity/timidity.cfg", + error); + if (path.IsNull()) + FatalError(error); + + if (!FileExists(path)) { + const auto utf8 = path.ToUTF8(); + FormatDebug(wildmidi_domain, + "configuration file does not exist: %s", + utf8.c_str()); + return false; + } + + return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0; +} + +static void +wildmidi_finish(void) +{ + WildMidi_Shutdown(); +} + +static void +wildmidi_file_decode(struct decoder *decoder, const char *path_fs) +{ + static constexpr AudioFormat audio_format = { + WILDMIDI_SAMPLE_RATE, + SampleFormat::S16, + 2, + }; + midi *wm; + const struct _WM_Info *info; + + wm = WildMidi_Open(path_fs); + if (wm == nullptr) + return; + + info = WildMidi_GetInfo(wm); + if (info == nullptr) { + WildMidi_Close(wm); + return; + } + + decoder_initialized(decoder, audio_format, true, + info->approx_total_samples / WILDMIDI_SAMPLE_RATE); + + DecoderCommand cmd; + do { + char buffer[4096]; + int len; + + info = WildMidi_GetInfo(wm); + if (info == nullptr) + break; + + len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); + if (len <= 0) + break; + + cmd = decoder_data(decoder, nullptr, buffer, len, 0); + + if (cmd == DecoderCommand::SEEK) { + unsigned long seek_where = WILDMIDI_SAMPLE_RATE * + decoder_seek_where(decoder); + +#ifdef HAVE_WILDMIDI_SAMPLED_SEEK + WildMidi_SampledSeek(wm, &seek_where); +#else + WildMidi_FastSeek(wm, &seek_where); +#endif + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + + } while (cmd == DecoderCommand::NONE); + + WildMidi_Close(wm); +} + +static bool +wildmidi_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + midi *wm = WildMidi_Open(path_fs); + if (wm == nullptr) + return false; + + const struct _WM_Info *info = WildMidi_GetInfo(wm); + if (info == nullptr) { + WildMidi_Close(wm); + return false; + } + + int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; + tag_handler_invoke_duration(handler, handler_ctx, duration); + + WildMidi_Close(wm); + + return true; +} + +static const char *const wildmidi_suffixes[] = { + "mid", + nullptr +}; + +const struct decoder_plugin wildmidi_decoder_plugin = { + "wildmidi", + wildmidi_init, + wildmidi_finish, + nullptr, + wildmidi_file_decode, + wildmidi_scan_file, + nullptr, + nullptr, + wildmidi_suffixes, + nullptr, +}; diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/WildmidiDecoderPlugin.hxx new file mode 100644 index 000000000..956b72299 --- /dev/null +++ b/src/decoder/WildmidiDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_WILDMIDI_HXX +#define MPD_DECODER_WILDMIDI_HXX + +extern const struct decoder_plugin wildmidi_decoder_plugin; + +#endif diff --git a/src/decoder/XiphTags.cxx b/src/decoder/XiphTags.cxx new file mode 100644 index 000000000..b9958a19a --- /dev/null +++ b/src/decoder/XiphTags.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "XiphTags.hxx" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/XiphTags.hxx b/src/decoder/XiphTags.hxx new file mode 100644 index 000000000..606dfef10 --- /dev/null +++ b/src/decoder/XiphTags.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_XIPH_TAGS_HXX +#define MPD_XIPH_TAGS_HXX + +#include "check.h" +#include "tag/TagTable.hxx" + +extern const struct tag_table xiph_tags[]; + +#endif diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c deleted file mode 100644 index d7f0c4a8a..000000000 --- a/src/decoder/_flac_common.c +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * 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> - -#include <assert.h> - -void -flac_data_init(struct flac_data *data, struct decoder * decoder, - struct input_stream *input_stream) -{ - 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->decoder = decoder; - data->input_stream = input_stream; - data->tag = NULL; -} - -void -flac_data_deinit(struct flac_data *data) -{ - pcm_buffer_deinit(&data->buffer); - - if (data->tag != NULL) - tag_free(data->tag); -} - -static enum sample_format -flac_sample_format(unsigned bits_per_sample) -{ - switch (bits_per_sample) { - 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; - - default: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -static void -flac_got_stream_info(struct flac_data *data, - const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - if (data->initialized || data->unsupported) - 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; - } - - data->frame_size = audio_format_frame_size(&data->audio_format); - - 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) -{ - 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: - flac_got_stream_info(data, &block->data.stream_info); - break; - - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - 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)) - decoder_mixramp(data->decoder, replay_gain_db, - mixramp_start, mixramp_end); - - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, - &block->data.vorbis_comment); - - default: - break; - } -} - -void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status, - struct flac_data *data) -{ - if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) - return; - - g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); -} - -/** - * 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 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; - } - - data->frame_size = audio_format_frame_size(&data->audio_format); - - decoder_initialized(data->decoder, &data->audio_format, - data->input_stream->seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - - data->initialized = true; - - return true; -} - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes) -{ - enum decoder_command cmd; - void *buffer; - unsigned bit_rate; - - if (!data->initialized && !flac_got_first_frame(data, &frame->header)) - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - size_t buffer_size = frame->header.blocksize * data->frame_size; - buffer = pcm_buffer_get(&data->buffer, buffer_size); - - flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, - 0, frame->header.blocksize); - - 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; - - 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; -} diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h deleted file mode 100644 index 0d90ba656..000000000 --- a/src/decoder/_flac_common.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_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" - -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; - - /** - * 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; - - /** - * 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; - - /** - * The number of the next frame which is going to be decoded. - */ - FLAC__uint64 next_frame; - - FLAC__uint64 position; - struct decoder *decoder; - struct input_stream *input_stream; - struct tag *tag; -}; - -/* initializes a given FlacData struct */ -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); - -void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status, - struct flac_data *data); - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - 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 deleted file mode 100644 index 09d2712da..000000000 --- a/src/decoder/_ogg_common.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#include "config.h" -#include "_ogg_common.h" - -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) -{ - /* oggflac detection based on code in ogg123 and this post - * http://lists.xiph.org/pipermail/flac/2004-December/000393.html - * ogg123 trunk still doesn't have this patch as of June 2005 */ - unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); - if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; - - if ((memcmp(buf + 29, "FLAC", 4) == 0 && - memcmp(buf + 37, "fLaC", 4) == 0) || - memcmp(buf + 28, "FLAC", 4) == 0 || - memcmp(buf + 28, "fLaC", 4) == 0) - return FLAC; - - return VORBIS; -} diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h deleted file mode 100644 index 85e4ebba6..000000000 --- a/src/decoder/_ogg_common.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H - -#include "decoder_api.h" - -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; - -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); - -#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c deleted file mode 100644 index b344795e7..000000000 --- a/src/decoder/audiofile_decoder_plugin.c +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_handler.h" - -#include <audiofile.h> -#include <af_vfs.h> -#include <assert.h> -#include <glib.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "audiofile" - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -static int audiofile_get_duration(const char *file) -{ - int total_time; - AFfilehandle af_fp = afOpenFile(file, "r", NULL); - if (af_fp == AF_NULL_FILEHANDLE) { - return -1; - } - total_time = (int) - ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) - / afGetRate(af_fp, AF_DEFAULT_TRACK)); - afCloseFile(af_fp); - return total_time; -} - -static ssize_t -audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - GError *error = NULL; - size_t nbytes; - - nbytes = input_stream_lock_read(is, data, length, &error); - if (nbytes == 0 && error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - return -1; - } - - return nbytes; -} - -static AFfileoffset -audiofile_file_length(AFvirtualfile *vfile) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - return is->size; -} - -static AFfileoffset -audiofile_file_tell(AFvirtualfile *vfile) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - return is->offset; -} - -static void -audiofile_file_destroy(AFvirtualfile *vfile) -{ - assert(vfile->closure != NULL); - - vfile->closure = NULL; -} - -static AFfileoffset -audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - int whence = (is_relative ? SEEK_CUR : SEEK_SET); - if (input_stream_lock_seek(is, offset, whence, NULL)) { - return is->offset; - } else { - return -1; - } -} - -static AFvirtualfile * -setup_virtual_fops(struct input_stream *stream) -{ - AFvirtualfile *vf = g_malloc(sizeof(AFvirtualfile)); - vf->closure = stream; - vf->write = NULL; - vf->read = audiofile_file_read; - vf->length = audiofile_file_length; - vf->destroy = audiofile_file_destroy; - vf->seek = audiofile_file_seek; - vf->tell = audiofile_file_tell; - 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; - struct audio_format audio_format; - float total_time; - uint16_t bit_rate; - int ret; - char chunk[CHUNK_SIZE]; - enum decoder_command cmd; - - if (!is->seekable) { - g_warning("not seekable"); - return; - } - - vf = setup_virtual_fops(is); - - af_fp = afOpenVirtualFile(vf, "r", NULL); - if (af_fp == AF_NULL_FILEHANDLE) { - g_warning("failed to input stream\n"); - return; - } - - 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; - } - - frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - - total_time = ((float)frame_count / (float)audio_format.sample_rate); - - bit_rate = (uint16_t)(is->size * 8.0 / total_time / 1000.0 + 0.5); - - fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); - - decoder_initialized(decoder, &audio_format, true, total_time); - - do { - ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, - CHUNK_SIZE / fs); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, NULL, - chunk, ret * fs, - bit_rate); - - if (cmd == DECODE_COMMAND_SEEK) { - AFframecount frame = decoder_seek_where(decoder) * - audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); - - decoder_command_finished(decoder); - cmd = DECODE_COMMAND_NONE; - } - } while (cmd == DECODE_COMMAND_NONE); - - afCloseFile(af_fp); -} - -static bool -audiofile_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - int total_time = audiofile_get_duration(file); - - if (total_time < 0) { - g_debug("Failed to get total song time from: %s\n", - file); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const audiofile_suffixes[] = { - "wav", "au", "aiff", "aif", NULL -}; - -static const char *const audiofile_mime_types[] = { - "audio/x-wav", - "audio/x-aiff", - NULL -}; - -const struct decoder_plugin audiofile_decoder_plugin = { - .name = "audiofile", - .stream_decode = audiofile_stream_decode, - .scan_file = audiofile_scan_file, - .suffixes = audiofile_suffixes, - .mime_types = audiofile_mime_types, -}; diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c deleted file mode 100644 index 84471fb3a..000000000 --- a/src/decoder/dsdiff_decoder_plugin.c +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * This plugin decodes DSDIFF data (SACD) embedded in DFF files. - * The DFF code was modeled after the specification found here: - * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf - * - * All functions common to both DSD decoders have been moved to dsdlib - */ - -#include "config.h" -#include "dsdiff_decoder_plugin.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "util/bit_reverse.h" -#include "tag_handler.h" -#include "dsdlib.h" -#include "tag_handler.h" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "dsdiff" - -struct dsdiff_header { - struct dsdlib_id id; - uint32_t size_high, size_low; - struct dsdlib_id format; -}; - -struct dsdiff_chunk_header { - struct dsdlib_id id; - uint32_t size_high, size_low; -}; - -struct dsdiff_metadata { - unsigned sample_rate, channels; - bool bitreverse; - uint64_t chunk_size; -}; - -static bool lsbitfirst; - -static bool -dsdiff_init(const struct config_param *param) -{ - lsbitfirst = config_get_block_bool(param, "lsbitfirst", false); - return true; -} - -/** - * Read the "size" attribute from the specified header, converting it - * to the host byte order if needed. - */ -G_GNUC_CONST -static uint64_t -dsdiff_chunk_size(const struct dsdiff_chunk_header *header) -{ - return (((uint64_t)GUINT32_FROM_BE(header->size_high)) << 32) | - ((uint64_t)GUINT32_FROM_BE(header->size_low)); -} - -static bool -dsdiff_read_id(struct decoder *decoder, struct input_stream *is, - struct dsdlib_id *id) -{ - return dsdlib_read(decoder, is, id, sizeof(*id)); -} - -static bool -dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is, - struct dsdiff_chunk_header *header) -{ - return dsdlib_read(decoder, is, header, sizeof(*header)); -} - -static bool -dsdiff_read_payload(struct decoder *decoder, struct input_stream *is, - const struct dsdiff_chunk_header *header, - void *data, size_t length) -{ - uint64_t size = dsdiff_chunk_size(header); - if (size != (uint64_t)length) - return false; - - size_t nbytes = decoder_read(decoder, is, data, length); - return nbytes == length; -} - -/** - * Read and parse a "SND" chunk inside "PROP". - */ -static bool -dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, - goffset end_offset) -{ - struct dsdiff_chunk_header header; - while ((goffset)(is->offset + sizeof(header)) <= end_offset) { - if (!dsdiff_read_chunk_header(decoder, is, &header)) - return false; - - goffset chunk_end_offset = - is->offset + dsdiff_chunk_size(&header); - if (chunk_end_offset > end_offset) - return false; - - if (dsdlib_id_equals(&header.id, "FS ")) { - uint32_t sample_rate; - if (!dsdiff_read_payload(decoder, is, &header, - &sample_rate, - sizeof(sample_rate))) - return false; - - metadata->sample_rate = GUINT32_FROM_BE(sample_rate); - } else if (dsdlib_id_equals(&header.id, "CHNL")) { - uint16_t channels; - if (dsdiff_chunk_size(&header) < sizeof(channels) || - !dsdlib_read(decoder, is, - &channels, sizeof(channels)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - metadata->channels = GUINT16_FROM_BE(channels); - } else if (dsdlib_id_equals(&header.id, "CMPR")) { - struct dsdlib_id type; - if (dsdiff_chunk_size(&header) < sizeof(type) || - !dsdlib_read(decoder, is, - &type, sizeof(type)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - if (!dsdlib_id_equals(&type, "DSD ")) - /* only uncompressed DSD audio data - is implemented */ - return false; - } else { - /* ignore unknown chunk */ - - if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - } - } - - return is->offset == end_offset; -} - -/** - * Read and parse a "PROP" chunk. - */ -static bool -dsdiff_read_prop(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, - const struct dsdiff_chunk_header *prop_header) -{ - uint64_t prop_size = dsdiff_chunk_size(prop_header); - goffset end_offset = is->offset + prop_size; - - struct dsdlib_id prop_id; - if (prop_size < sizeof(prop_id) || - !dsdiff_read_id(decoder, is, &prop_id)) - return false; - - if (dsdlib_id_equals(&prop_id, "SND ")) - return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); - else - /* ignore unknown PROP chunk */ - return dsdlib_skip_to(decoder, is, end_offset); -} - -/** - * Read and parse all metadata chunks at the beginning. Stop when the - * first "DSD" chunk is seen, and return its header in the - * "chunk_header" parameter. - */ -static bool -dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, - struct dsdiff_chunk_header *chunk_header) -{ - struct dsdiff_header header; - if (!dsdlib_read(decoder, is, &header, sizeof(header)) || - !dsdlib_id_equals(&header.id, "FRM8") || - !dsdlib_id_equals(&header.format, "DSD ")) - return false; - - while (true) { - if (!dsdiff_read_chunk_header(decoder, is, - chunk_header)) - return false; - - if (dsdlib_id_equals(&chunk_header->id, "PROP")) { - if (!dsdiff_read_prop(decoder, is, metadata, - chunk_header)) - return false; - } else if (dsdlib_id_equals(&chunk_header->id, "DSD ")) { - uint64_t chunk_size; - chunk_size = dsdiff_chunk_size(chunk_header); - metadata->chunk_size = chunk_size; - return true; - } else { - /* ignore unknown chunk */ - uint64_t chunk_size; - chunk_size = dsdiff_chunk_size(chunk_header); - goffset chunk_end_offset = is->offset + chunk_size; - - if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - } - } -} - -static void -bit_reverse_buffer(uint8_t *p, uint8_t *end) -{ - for (; p < end; ++p) - *p = bit_reverse(*p); -} - -/** - * Decode one "DSD" chunk. - */ -static bool -dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is, - unsigned channels, - uint64_t chunk_size) -{ - uint8_t buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size > 0) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - unsigned now_frames = buffer_frames; - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - now_frames = (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; - } - - size_t nbytes = decoder_read(decoder, is, buffer, now_size); - if (nbytes != now_size) - return false; - - chunk_size -= nbytes; - - if (lsbitfirst) - bit_reverse_buffer(buffer, buffer + nbytes); - - enum decoder_command cmd = - decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DECODE_COMMAND_NONE: - break; - - case DECODE_COMMAND_START: - case DECODE_COMMAND_STOP: - return false; - - case DECODE_COMMAND_SEEK: - - /* Not implemented yet */ - decoder_seek_error(decoder); - break; - } - } - return dsdlib_skip(decoder, is, chunk_size); -} - -static void -dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is) -{ - struct dsdiff_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; - - struct dsdiff_chunk_header chunk_header; - /* check if it is is a proper DFF file */ - if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header)) - return; - - GError *error = NULL; - struct audio_format audio_format; - if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, - SAMPLE_FORMAT_DSD, - metadata.channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - /* calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; - - /* success: file was recognized */ - decoder_initialized(decoder, &audio_format, false, songtime); - - /* every iteration of the following loop decodes one "DSD" - chunk from a DFF file */ - - while (true) { - chunk_size = dsdiff_chunk_size(&chunk_header); - - if (dsdlib_id_equals(&chunk_header.id, "DSD ")) { - if (!dsdiff_decode_chunk(decoder, is, - metadata.channels, - chunk_size)) - break; - } else { - /* ignore other chunks */ - if (!dsdlib_skip(decoder, is, chunk_size)) - break; - } - - /* read next chunk header; the first one was read by - dsdiff_read_metadata() */ - if (!dsdiff_read_chunk_header(decoder, - is, &chunk_header)) - break; - } -} - -static bool -dsdiff_scan_stream(struct input_stream *is, - G_GNUC_UNUSED const struct tag_handler *handler, - G_GNUC_UNUSED void *handler_ctx) -{ - struct dsdiff_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; - - struct dsdiff_chunk_header chunk_header; - /* First check for DFF metadata */ - if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header)) - return false; - - struct audio_format audio_format; - if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, - SAMPLE_FORMAT_DSD, - metadata.channels, NULL)) - /* refuse to parse files which we cannot play anyway */ - return false; - - /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; - tag_handler_invoke_duration(handler, handler_ctx, songtime); - - return true; -} - -static const char *const dsdiff_suffixes[] = { - "dff", - NULL -}; - -static const char *const dsdiff_mime_types[] = { - "application/x-dff", - NULL -}; - -const struct decoder_plugin dsdiff_decoder_plugin = { - .name = "dsdiff", - .init = dsdiff_init, - .stream_decode = dsdiff_stream_decode, - .scan_stream = dsdiff_scan_stream, - .suffixes = dsdiff_suffixes, - .mime_types = dsdiff_mime_types, -}; diff --git a/src/decoder/dsdiff_decoder_plugin.h b/src/decoder/dsdiff_decoder_plugin.h deleted file mode 100644 index 452f9050b..000000000 --- a/src/decoder/dsdiff_decoder_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_DSDIFF_H -#define MPD_DECODER_DSDIFF_H - -extern const struct decoder_plugin dsdiff_decoder_plugin; - -#endif diff --git a/src/decoder/dsdlib.c b/src/decoder/dsdlib.c deleted file mode 100644 index 3df9497c4..000000000 --- a/src/decoder/dsdlib.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * This file contains functions used by the DSF and DSDIFF decoders. - * - */ - -#include "config.h" -#include "dsf_decoder_plugin.h" -#include "decoder_api.h" -#include "util/bit_reverse.h" -#include "dsdlib.h" -#include "dsdiff_decoder_plugin.h" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -bool -dsdlib_id_equals(const struct dsdlib_id *id, const char *s) -{ - assert(id != NULL); - assert(s != NULL); - assert(strlen(s) == sizeof(id->value)); - - return memcmp(id->value, s, sizeof(id->value)) == 0; -} - -bool -dsdlib_read(struct decoder *decoder, struct input_stream *is, - void *data, size_t length) -{ - size_t nbytes = decoder_read(decoder, is, data, length); - return nbytes == length; -} - -/** - * Skip the #input_stream to the specified offset. - */ -bool -dsdlib_skip_to(struct decoder *decoder, struct input_stream *is, - goffset offset) -{ - if (is->seekable) - return input_stream_seek(is, offset, SEEK_SET, NULL); - - if (is->offset > offset) - return false; - - char buffer[8192]; - while (is->offset < offset) { - size_t length = sizeof(buffer); - if (offset - is->offset < (goffset)length) - length = offset - is->offset; - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - } - - assert(is->offset == offset); - return true; -} - -/** - * Skip some bytes from the #input_stream. - */ -bool -dsdlib_skip(struct decoder *decoder, struct input_stream *is, - goffset delta) -{ - assert(delta >= 0); - - if (delta == 0) - return true; - - if (is->seekable) - return input_stream_seek(is, delta, SEEK_CUR, NULL); - - char buffer[8192]; - while (delta > 0) { - size_t length = sizeof(buffer); - if ((goffset)length > delta) - length = delta; - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - - delta -= nbytes; - } - - return true; -} - diff --git a/src/decoder/dsdlib.h b/src/decoder/dsdlib.h deleted file mode 100644 index d9675f5fe..000000000 --- a/src/decoder/dsdlib.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_DSDLIB_H -#define MPD_DECODER_DSDLIB_H - -struct dsdlib_id { - char value[4]; -}; - -bool -dsdlib_id_equals(const struct dsdlib_id *id, const char *s); - -bool -dsdlib_read(struct decoder *decoder, struct input_stream *is, - void *data, size_t length); - -bool -dsdlib_skip_to(struct decoder *decoder, struct input_stream *is, - goffset offset); - -bool -dsdlib_skip(struct decoder *decoder, struct input_stream *is, - goffset delta); - -#endif diff --git a/src/decoder/dsf_decoder_plugin.c b/src/decoder/dsf_decoder_plugin.c deleted file mode 100644 index c0107eb30..000000000 --- a/src/decoder/dsf_decoder_plugin.c +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * This plugin decodes DSDIFF data (SACD) embedded in DSF files. - * - * The DSF code was created using the specification found here: - * http://dsd-guide.com/sonys-dsf-file-format-spec - * - * All functions common to both DSD decoders have been moved to dsdlib - */ - -#include "config.h" -#include "dsf_decoder_plugin.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "util/bit_reverse.h" -#include "dsdlib.h" -#include "tag_handler.h" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "dsf" - -struct dsf_metadata { - unsigned sample_rate, channels; - bool bitreverse; - uint64_t chunk_size; -}; - -struct dsf_header { - /** DSF header id: "DSD " */ - struct dsdlib_id id; - /** DSD chunk size, including id = 28 */ - uint32_t size_low, size_high; - /** total file size */ - uint32_t fsize_low, fsize_high; - /** pointer to id3v2 metadata, should be at the end of the file */ - uint32_t pmeta_low, pmeta_high; -}; -/** DSF file fmt chunk */ -struct dsf_fmt_chunk { - - /** id: "fmt " */ - struct dsdlib_id id; - /** fmt chunk size, including id, normally 52 */ - uint32_t size_low, size_high; - /** version of this format = 1 */ - uint32_t version; - /** 0: DSD raw */ - uint32_t formatid; - /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */ - uint32_t channeltype; - /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */ - uint32_t channelnum; - /** sample frequency: 2822400, 5644800 */ - uint32_t sample_freq; - /** bits per sample 1 or 8 */ - uint32_t bitssample; - /** Sample count per channel in bytes */ - uint32_t scnt_low, scnt_high; - /** block size per channel = 4096 */ - uint32_t block_size; - /** reserved, should be all zero */ - uint32_t reserved; -}; - -struct dsf_data_chunk { - struct dsdlib_id id; - /** "data" chunk size, includes header (id+size) */ - uint32_t size_low, size_high; -}; - -/** - * Read and parse all needed metadata chunks for DSF files. - */ -static bool -dsf_read_metadata(struct decoder *decoder, struct input_stream *is, - struct dsf_metadata *metadata) -{ - uint64_t chunk_size; - struct dsf_header dsf_header; - if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) || - !dsdlib_id_equals(&dsf_header.id, "DSD ")) - return false; - - chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_header.size_high)) << 32) | - ((uint64_t)GUINT32_FROM_LE(dsf_header.size_low)); - - if (sizeof(dsf_header) != chunk_size) - return false; - - /* read the 'fmt ' chunk of the DSF file */ - struct dsf_fmt_chunk dsf_fmt_chunk; - if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || - !dsdlib_id_equals(&dsf_fmt_chunk.id, "fmt ")) - return false; - - uint64_t fmt_chunk_size; - fmt_chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_high)) << 32) | - ((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_low)); - - if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) - return false; - - uint32_t samplefreq = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.sample_freq); - - /* for now, only support version 1 of the standard, DSD raw stereo - files with a sample freq of 2822400 Hz */ - - if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 - || dsf_fmt_chunk.channeltype != 2 - || dsf_fmt_chunk.channelnum != 2 - || samplefreq != 2822400) - return false; - - uint32_t chblksize = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.block_size); - /* according to the spec block size should always be 4096 */ - if (chblksize != 4096) - return false; - - /* read the 'data' chunk of the DSF file */ - struct dsf_data_chunk data_chunk; - if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) || - !dsdlib_id_equals(&data_chunk.id, "data")) - return false; - - /* data size of DSF files are padded to multiple of 4096, - we use the actual data size as chunk size */ - - uint64_t data_size; - data_size = (((uint64_t)GUINT32_FROM_LE(data_chunk.size_high)) << 32) | - ((uint64_t)GUINT32_FROM_LE(data_chunk.size_low)); - data_size -= sizeof(data_chunk); - - metadata->chunk_size = data_size; - metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; - metadata->sample_rate = samplefreq; - - /* check bits per sample format, determine if bitreverse is needed */ - metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; - return true; -} - -static void -bit_reverse_buffer(uint8_t *p, uint8_t *end) -{ - for (; p < end; ++p) - *p = bit_reverse(*p); -} - -/** - * DSF data is build up of alternating 4096 blocks of DSD samples for left and - * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 - * block of 4096 DSD right samples to 8k of samples in normal PCM left/right - * order. - */ -static void -dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) -{ - for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { - scratch[i] = *(dest+j); - j++; - } - - for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { - scratch[i] = *(dest+4096+j); - j++; - } - - for (unsigned i = 0; i < (unsigned)nrbytes; i++) { - *dest = scratch[i]; - dest++; - } -} - -/** - * Decode one complete DSF 'data' chunk i.e. a complete song - */ -static bool -dsf_decode_chunk(struct decoder *decoder, struct input_stream *is, - unsigned channels, - uint64_t chunk_size, - bool bitreverse) -{ - uint8_t buffer[8192]; - - /* scratch buffer for DSF samples to convert to the needed - normal left/right regime of samples */ - uint8_t dsf_scratch_buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size > 0) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - unsigned now_frames = buffer_frames; - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - now_frames = (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; - } - - size_t nbytes = decoder_read(decoder, is, buffer, now_size); - if (nbytes != now_size) - return false; - - chunk_size -= nbytes; - - if (bitreverse) - bit_reverse_buffer(buffer, buffer + nbytes); - - dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes); - - enum decoder_command cmd = - decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DECODE_COMMAND_NONE: - break; - - case DECODE_COMMAND_START: - case DECODE_COMMAND_STOP: - return false; - - case DECODE_COMMAND_SEEK: - - /* not implemented yet */ - decoder_seek_error(decoder); - break; - } - } - return dsdlib_skip(decoder, is, chunk_size); -} - -static void -dsf_stream_decode(struct decoder *decoder, struct input_stream *is) -{ - struct dsf_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; - - /* check if it is a proper DSF file */ - if (!dsf_read_metadata(decoder, is, &metadata)) - return; - - GError *error = NULL; - struct audio_format audio_format; - if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, - SAMPLE_FORMAT_DSD, - metadata.channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - /* Calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; - - /* success: file was recognized */ - decoder_initialized(decoder, &audio_format, false, songtime); - - if (!dsf_decode_chunk(decoder, is, metadata.channels, - metadata.chunk_size, - metadata.bitreverse)) - return; -} - -static bool -dsf_scan_stream(struct input_stream *is, - G_GNUC_UNUSED const struct tag_handler *handler, - G_GNUC_UNUSED void *handler_ctx) -{ - struct dsf_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; - - /* check DSF metadata */ - if (!dsf_read_metadata(NULL, is, &metadata)) - return false; - - struct audio_format audio_format; - if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, - SAMPLE_FORMAT_DSD, - metadata.channels, NULL)) - /* refuse to parse files which we cannot play anyway */ - return false; - - /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; - tag_handler_invoke_duration(handler, handler_ctx, songtime); - - return true; -} - -static const char *const dsf_suffixes[] = { - "dsf", - NULL -}; - -static const char *const dsf_mime_types[] = { - "application/x-dsf", - NULL -}; - -const struct decoder_plugin dsf_decoder_plugin = { - .name = "dsf", - .stream_decode = dsf_stream_decode, - .scan_stream = dsf_scan_stream, - .suffixes = dsf_suffixes, - .mime_types = dsf_mime_types, -}; diff --git a/src/decoder/dsf_decoder_plugin.h b/src/decoder/dsf_decoder_plugin.h deleted file mode 100644 index 401d3fed7..000000000 --- a/src/decoder/dsf_decoder_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_DSF_H -#define MPD_DECODER_DSF_H - -extern const struct decoder_plugin dsf_decoder_plugin; - -#endif diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c deleted file mode 100644 index 911f033b8..000000000 --- a/src/decoder/faad_decoder_plugin.c +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "decoder_buffer.h" -#include "audio_check.h" -#include "tag_handler.h" - -#define AAC_MAX_CHANNELS 6 - -#include <assert.h> -#include <unistd.h> -#include <faad.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "faad" - -static const unsigned adts_sample_rates[] = - { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, 7350, 0, 0, 0 -}; - -/** - * 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. - */ -static size_t -adts_check_frame(const unsigned char *data) -{ - /* check syncword */ - if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) - return 0; - - return (((unsigned int)data[3] & 0x3) << 11) | - (((unsigned int)data[4]) << 3) | - (data[5] >> 5); -} - -/** - * Find the next AAC frame in the buffer. Returns 0 if no frame is - * found or if not enough data is available. - */ -static size_t -adts_find_frame(struct decoder_buffer *buffer) -{ - const unsigned char *data, *p; - size_t length, frame_length; - bool ret; - - while (true) { - data = decoder_buffer_read(buffer, &length); - if (data == NULL || length < 8) { - /* not enough data yet */ - ret = decoder_buffer_fill(buffer); - if (!ret) - /* failed */ - return 0; - - continue; - } - - /* find the 0xff marker */ - p = memchr(data, 0xff, length); - if (p == NULL) { - /* no marker - discard the buffer */ - decoder_buffer_consume(buffer, length); - continue; - } - - if (p > data) { - /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data); - continue; - } - - /* is it a frame? */ - frame_length = adts_check_frame(data); - if (frame_length == 0) { - /* it's just some random 0xff byte; discard it - and continue searching */ - decoder_buffer_consume(buffer, 1); - continue; - } - - if (length < frame_length) { - /* available buffer size is smaller than the - frame will be - attempt to read more - data */ - ret = decoder_buffer_fill(buffer); - if (!ret) { - /* not enough data; discard this frame - to prevent a possible buffer - overflow */ - data = decoder_buffer_read(buffer, &length); - if (data != NULL) - decoder_buffer_consume(buffer, length); - } - - continue; - } - - /* found a full frame! */ - return frame_length; - } -} - -static float -adts_song_duration(struct decoder_buffer *buffer) -{ - unsigned int frames, frame_length; - unsigned sample_rate = 0; - float frames_per_second; - - /* Read all frames to ensure correct time and bitrate */ - for (frames = 0;; frames++) { - frame_length = adts_find_frame(buffer); - if (frame_length == 0) - break; - - - if (frames == 0) { - const unsigned char *data; - size_t buffer_length; - - data = decoder_buffer_read(buffer, &buffer_length); - assert(data != NULL); - assert(frame_length <= buffer_length); - - sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; - } - - decoder_buffer_consume(buffer, frame_length); - } - - frames_per_second = (float)sample_rate / 1024.0; - if (frames_per_second <= 0) - return -1; - - return (float)frames / frames_per_second; -} - -static float -faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) -{ - size_t fileread; - size_t tagsize; - const unsigned char *data; - size_t length; - bool success; - - fileread = is->size >= 0 ? is->size : 0; - - decoder_buffer_fill(buffer); - data = decoder_buffer_read(buffer, &length); - if (data == NULL) - return -1; - - tagsize = 0; - if (length >= 10 && !memcmp(data, "ID3", 3)) { - /* skip the ID3 tag */ - - tagsize = (data[6] << 21) | (data[7] << 14) | - (data[8] << 7) | (data[9] << 0); - - tagsize += 10; - - success = decoder_buffer_skip(buffer, tagsize) && - decoder_buffer_fill(buffer); - if (!success) - return -1; - - data = decoder_buffer_read(buffer, &length); - if (data == NULL) - return -1; - } - - if (is->seekable && length >= 2 && - data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) { - /* obtain the duration from the ADTS header */ - float song_length = adts_song_duration(buffer); - - input_stream_lock_seek(is, tagsize, SEEK_SET, NULL); - - data = decoder_buffer_read(buffer, &length); - if (data != NULL) - decoder_buffer_consume(buffer, length); - decoder_buffer_fill(buffer); - - return song_length; - } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { - /* obtain the duration from the ADIF header */ - unsigned bit_rate; - size_t skip_size = (data[4] & 0x80) ? 9 : 0; - - if (8 + skip_size > length) - /* not enough data yet; skip parsing this - header */ - return -1; - - bit_rate = ((data[4 + skip_size] & 0x0F) << 19) | - (data[5 + skip_size] << 11) | - (data[6 + skip_size] << 3) | - (data[7 + skip_size] & 0xE0); - - if (fileread != 0 && bit_rate != 0) - return fileread * 8.0 / bit_rate; - else - return fileread; - } else - return -1; -} - -/** - * Wrapper for faacDecInit() which works around some API - * inconsistencies in libfaad. - */ -static bool -faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, - struct audio_format *audio_format, GError **error_r) -{ - union { - /* deconst hack for libfaad */ - const void *in; - void *out; - } u; - size_t length; - int32_t nbytes; - uint32_t sample_rate; - uint8_t channels; -#ifdef HAVE_FAAD_LONG - /* 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_p = (unsigned long *)(void *)&sample_rate; -#else - uint32_t *sample_rate_p = &sample_rate; -#endif - - u.in = decoder_buffer_read(buffer, &length); - 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_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); - - return audio_format_init_checked(audio_format, sample_rate, - SAMPLE_FORMAT_S16, channels, error_r); -} - -/** - * Wrapper for faacDecDecode() which works around some API - * inconsistencies in libfaad. - */ -static const void * -faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, - faacDecFrameInfo *frame_info) -{ - union { - /* deconst hack for libfaad */ - const void *in; - void *out; - } u; - size_t length; - void *result; - - u.in = decoder_buffer_read(buffer, &length); - if (u.in == NULL) - return NULL; - - result = faacDecDecode(decoder, frame_info, - u.out -#ifdef HAVE_FAAD_BUFLEN_FUNCS - , length -#endif - ); - - return result; -} - -/** - * Get a song file's total playing time in seconds, as a float. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static float -faad_get_file_time_float(struct input_stream *is) -{ - struct decoder_buffer *buffer; - float length; - faacDecHandle decoder; - faacDecConfigurationPtr config; - - buffer = decoder_buffer_new(NULL, is, - FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - length = faad_song_duration(buffer, is); - - if (length < 0) { - bool ret; - struct audio_format audio_format; - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder, config); - - decoder_buffer_fill(buffer); - - ret = faad_decoder_init(decoder, buffer, &audio_format, NULL); - if (ret) - length = 0; - - faacDecClose(decoder); - } - - decoder_buffer_free(buffer); - - return length; -} - -/** - * Get a song file's total playing time in seconds, as an int. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static int -faad_get_file_time(struct input_stream *is) -{ - int file_time = -1; - float length; - - if ((length = faad_get_file_time_float(is)) >= 0) - file_time = length + 0.5; - - return file_time; -} - -static void -faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) -{ - GError *error = NULL; - float total_time = 0; - faacDecHandle decoder; - struct audio_format audio_format; - faacDecConfigurationPtr config; - bool ret; - uint16_t bit_rate = 0; - struct decoder_buffer *buffer; - enum decoder_command cmd; - - buffer = decoder_buffer_new(mpd_decoder, is, - FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - total_time = faad_song_duration(buffer, is); - - /* create the libfaad decoder */ - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX - config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR - config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); - - while (!decoder_buffer_is_full(buffer) && - !input_stream_lock_eof(is) && - decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { - adts_find_frame(buffer); - decoder_buffer_fill(buffer); - } - - /* initialize it */ - - ret = faad_decoder_init(decoder, buffer, &audio_format, &error); - if (!ret) { - g_warning("%s", error->message); - g_error_free(error); - faacDecClose(decoder); - return; - } - - /* initialize the MPD core */ - - decoder_initialized(mpd_decoder, &audio_format, false, total_time); - - /* the decoder loop */ - - do { - size_t frame_size; - const void *decoded; - faacDecFrameInfo frame_info; - - /* find the next frame */ - - frame_size = adts_find_frame(buffer); - if (frame_size == 0) - /* end of file */ - break; - - /* decode it */ - - decoded = faad_decoder_decode(decoder, buffer, &frame_info); - - if (frame_info.error > 0) { - g_warning("error decoding AAC stream: %s\n", - faacDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - g_warning("channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - if (frame_info.samplerate != audio_format.sample_rate) { - g_warning("sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } -#endif - - decoder_buffer_consume(buffer, frame_info.bytesconsumed); - - /* update bit rate and position */ - - if (frame_info.samples > 0) { - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * audio_format.sample_rate / - frame_info.samples / 1000 + 0.5; - } - - /* send PCM samples to MPD */ - - cmd = decoder_data(mpd_decoder, is, decoded, - (size_t)frame_info.samples * 2, - bit_rate); - } while (cmd != DECODE_COMMAND_STOP); - - /* cleanup */ - - faacDecClose(decoder); -} - -static bool -faad_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - int file_time = faad_get_file_time(is); - - if (file_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, file_time); - return true; -} - -static const char *const faad_suffixes[] = { "aac", NULL }; -static const char *const faad_mime_types[] = { - "audio/aac", "audio/aacp", NULL -}; - -const struct decoder_plugin faad_decoder_plugin = { - .name = "faad", - .stream_decode = faad_stream_decode, - .scan_stream = faad_scan_stream, - .suffixes = faad_suffixes, - .mime_types = faad_mime_types, -}; diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c deleted file mode 100644 index 58bd2f54a..000000000 --- a/src/decoder/ffmpeg_decoder_plugin.c +++ /dev/null @@ -1,814 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "ffmpeg_metadata.h" -#include "tag_handler.h" - -#include <glib.h> - -#include <assert.h> -#include <stdio.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libavformat/avio.h> -#include <libavutil/avutil.h> -#include <libavutil/log.h> -#include <libavutil/mathematics.h> -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) -#include <libavutil/dict.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ffmpeg" - -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); - } -} - -struct mpd_ffmpeg_stream { - struct decoder *decoder; - struct input_stream *input; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) - AVIOContext *io; -#else - ByteIOContext *io; -#endif - unsigned char buffer[8192]; -}; - -static int -mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) -{ - struct mpd_ffmpeg_stream *stream = opaque; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t -mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) -{ - struct mpd_ffmpeg_stream *stream = opaque; - - if (whence == AVSEEK_SIZE) - return stream->input->size; - - if (!input_stream_lock_seek(stream->input, pos, whence, NULL)) - return -1; - - return stream->input->offset; -} - -static struct mpd_ffmpeg_stream * -mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input) -{ - struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1); - stream->decoder = decoder; - stream->input = input; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) - stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer), - false, stream, - mpd_ffmpeg_stream_read, NULL, - input->seekable - ? mpd_ffmpeg_stream_seek : NULL); -#else - stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer), - false, stream, - mpd_ffmpeg_stream_read, NULL, - input->seekable - ? mpd_ffmpeg_stream_seek : NULL); -#endif - if (stream->io == NULL) { - g_free(stream); - return NULL; - } - - return stream; -} - -/** - * API compatibility wrapper for av_open_input_stream() and - * avformat_open_input(). - */ -static int -mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) - AVIOContext *pb, -#else - ByteIOContext *pb, -#endif - const char *filename, - AVInputFormat *fmt) -{ -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,1,3) - AVFormatContext *context = avformat_alloc_context(); - if (context == NULL) - return AVERROR(ENOMEM); - - context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, NULL); -#else - return av_open_input_stream(ic_ptr, pb, filename, fmt, NULL); -#endif -} - -static void -mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream) -{ - av_free(stream->io); - g_free(stream); -} - -static bool -ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) -{ - av_log_set_callback(mpd_ffmpeg_log_callback); - - av_register_all(); - return true; -} - -static int -ffmpeg_find_audio_stream(const AVFormatContext *format_context) -{ - for (unsigned i = 0; i < format_context->nb_streams; ++i) - if (format_context->streams[i]->codec->codec_type == -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0) - AVMEDIA_TYPE_AUDIO) -#else - CODEC_TYPE_AUDIO) -#endif - return i; - - return -1; -} - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,25,0) -/** - * 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; -} -#endif - -G_GNUC_CONST -static double -time_from_ffmpeg(int64_t t, const AVRational time_base) -{ - assert(t != (int64_t)AV_NOPTS_VALUE); - - return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) - / (double)1024; -} - -G_GNUC_CONST -static int64_t -time_to_ffmpeg(double t, const AVRational time_base) -{ - return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, - time_base); -} - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) - -static void -copy_interleave_frame2(uint8_t *dest, uint8_t **src, - unsigned nframes, unsigned nchannels, - unsigned sample_size) -{ - for (unsigned frame = 0; frame < nframes; ++frame) { - for (unsigned channel = 0; channel < nchannels; ++channel) { - memcpy(dest, src[channel] + frame * sample_size, - sample_size); - dest += sample_size; - } - } -} - -/** - * Copy PCM data from a AVFrame to an interleaved buffer. - */ -static int -copy_interleave_frame(const AVCodecContext *codec_context, - const AVFrame *frame, - uint8_t *buffer, size_t buffer_size) -{ - int plane_size; - const int data_size = - av_samples_get_buffer_size(&plane_size, - codec_context->channels, - frame->nb_samples, - codec_context->sample_fmt, 1); - if (buffer_size < (size_t)data_size) - /* buffer is too small - shouldn't happen */ - return AVERROR(EINVAL); - - if (av_sample_fmt_is_planar(codec_context->sample_fmt) && - codec_context->channels > 1) { - copy_interleave_frame2(buffer, frame->extended_data, - frame->nb_samples, - codec_context->channels, - av_get_bytes_per_sample(codec_context->sample_fmt)); - } else { - memcpy(buffer, frame->extended_data[0], data_size); - } - - return data_size; -} -#endif - -static enum decoder_command -ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVRational *time_base) -{ - if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) - decoder_timestamp(decoder, - time_from_ffmpeg(packet->pts, *time_base)); - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - AVPacket packet2 = *packet; -#else - const uint8_t *packet_data = packet->data; - int packet_size = packet->size; -#endif - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) - uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; - const size_t buffer_size = sizeof(aligned_buffer); -#else - /* libavcodec < 0.8 needs an aligned buffer */ - uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; - size_t buffer_size = sizeof(audio_buf); - int16_t *aligned_buffer = align16(audio_buf, &buffer_size); -#endif - - enum decoder_command cmd = DECODE_COMMAND_NONE; - while ( -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - packet2.size > 0 && -#else - packet_size > 0 && -#endif - cmd == DECODE_COMMAND_NONE) { - int audio_size = buffer_size; -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0) - - AVFrame *frame = avcodec_alloc_frame(); - if (frame == NULL) { - g_warning("Could not allocate frame"); - break; - } - - int got_frame = 0; - int len = avcodec_decode_audio4(codec_context, - frame, &got_frame, - &packet2); - if (len >= 0 && got_frame) { - audio_size = copy_interleave_frame(codec_context, - frame, - aligned_buffer, - buffer_size); - if (audio_size < 0) - len = audio_size; - } else if (len >= 0) - len = -1; - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) - avcodec_free_frame(&frame); -#else - av_freep(&frame); -#endif - -#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - int len = avcodec_decode_audio3(codec_context, - aligned_buffer, &audio_size, - &packet2); -#else - int len = avcodec_decode_audio2(codec_context, - aligned_buffer, &audio_size, - packet_data, packet_size); -#endif - - if (len < 0) { - /* if error, we skip the frame */ - g_message("decoding failed, frame skipped\n"); - break; - } - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0) - packet2.data += len; - packet2.size -= len; -#else - packet_data += len; - packet_size -= len; -#endif - - if (audio_size <= 0) - continue; - - cmd = decoder_data(decoder, is, - aligned_buffer, audio_size, - codec_context->bit_rate / 1000); - } - return cmd; -} - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 94, 1) -#define AVSampleFormat SampleFormat -#endif - -G_GNUC_CONST -static enum sample_format -ffmpeg_sample_format(enum AVSampleFormat sample_fmt) -{ - switch (sample_fmt) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) - case AV_SAMPLE_FMT_S16: -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) - case AV_SAMPLE_FMT_S16P: -#endif -#else - case SAMPLE_FMT_S16: -#endif - return SAMPLE_FORMAT_S16; - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) - case AV_SAMPLE_FMT_S32: -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) - case AV_SAMPLE_FMT_S32P: -#endif -#else - case SAMPLE_FMT_S32: -#endif - return SAMPLE_FORMAT_S32; - -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0) - case AV_SAMPLE_FMT_FLTP: - return SAMPLE_FORMAT_FLOAT; -#endif - - default: - break; - } - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) - char buffer[64]; - const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), - sample_fmt); - if (name != NULL) - g_warning("Unsupported libavcodec SampleFormat value: %s (%d)", - name, sample_fmt); - else -#endif - g_warning("Unsupported libavcodec SampleFormat value: %d", - sample_fmt); - return SAMPLE_FORMAT_UNDEFINED; -} - -static AVInputFormat * -ffmpeg_probe(struct decoder *decoder, struct input_stream *is) -{ - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; - - unsigned char *buffer = g_malloc(BUFFER_SIZE); - size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || - !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) { - g_free(buffer); - return NULL; - } - - /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes - beyond the declared buffer limit, which makes valgrind - angry; this workaround removes some padding from the buffer - size */ - nbytes -= PADDING; - - AVProbeData avpd = { - .buf = buffer, - .buf_size = nbytes, - .filename = is->uri, - }; - - AVInputFormat *format = av_probe_input_format(&avpd, true); - g_free(buffer); - - return format; -} - -static void -ffmpeg_decode(struct decoder *decoder, struct input_stream *input) -{ - AVInputFormat *input_format = ffmpeg_probe(decoder, input); - if (input_format == NULL) - return; - - g_debug("detected input format '%s' (%s)", - input_format->name, input_format->long_name); - - struct mpd_ffmpeg_stream *stream = - mpd_ffmpeg_stream_open(decoder, input); - if (stream == NULL) { - g_warning("Failed to open stream"); - return; - } - - //ffmpeg works with ours "fileops" helper - AVFormatContext *format_context = NULL; - if (mpd_ffmpeg_open_input(&format_context, stream->io, input->uri, - input_format) != 0) { - g_warning("Open failed\n"); - mpd_ffmpeg_stream_close(stream); - return; - } - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) - const int find_result = - avformat_find_stream_info(format_context, NULL); -#else - const int find_result = av_find_stream_info(format_context); -#endif - if (find_result < 0) { - g_warning("Couldn't find stream info\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - int audio_stream = ffmpeg_find_audio_stream(format_context); - if (audio_stream == -1) { - g_warning("No audio stream inside\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - AVStream *av_stream = format_context->streams[audio_stream]; - - AVCodecContext *codec_context = av_stream->codec; - if (codec_context->codec_name[0] != 0) - g_debug("codec '%s'", codec_context->codec_name); - - AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); - - if (!codec) { - g_warning("Unsupported audio codec\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - const enum sample_format sample_format = - ffmpeg_sample_format(codec_context->sample_fmt); - if (sample_format == SAMPLE_FORMAT_UNDEFINED) - return; - - GError *error = NULL; - struct audio_format audio_format; - if (!audio_format_init_checked(&audio_format, - codec_context->sample_rate, - sample_format, - codec_context->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - /* the audio format must be read from AVCodecContext by now, - because avcodec_open() has been demonstrated to fill bogus - values into AVCodecContext.channels - a change that will be - reverted later by avcodec_decode_audio3() */ - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,6,0) - const int open_result = avcodec_open2(codec_context, codec, NULL); -#else - const int open_result = avcodec_open(codec_context, codec); -#endif - if (open_result < 0) { - g_warning("Could not open codec\n"); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); - return; - } - - int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE - ? format_context->duration / AV_TIME_BASE - : 0; - - decoder_initialized(decoder, &audio_format, - 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 == audio_stream) - cmd = ffmpeg_send_packet(decoder, input, - &packet, codec_context, - &av_stream->time_base); - else - cmd = decoder_get_command(decoder); - - av_free_packet(&packet); - - if (cmd == DECODE_COMMAND_SEEK) { - int64_t where = - time_to_ffmpeg(decoder_seek_where(decoder), - av_stream->time_base); - - if (av_seek_frame(format_context, audio_stream, where, - AV_TIME_BASE) < 0) - decoder_seek_error(decoder); - else { - avcodec_flush_buffers(codec_context); - decoder_command_finished(decoder); - } - } - } while (cmd != DECODE_COMMAND_STOP); - - avcodec_close(codec_context); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&format_context); -#else - av_close_input_stream(format_context); -#endif - mpd_ffmpeg_stream_close(stream); -} - -//no tag reading in ffmpeg, check if playable -static bool -ffmpeg_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - AVInputFormat *input_format = ffmpeg_probe(NULL, is); - if (input_format == NULL) - return false; - - struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is); - if (stream == NULL) - return false; - - AVFormatContext *f = NULL; - if (mpd_ffmpeg_open_input(&f, stream->io, is->uri, - input_format) != 0) { - mpd_ffmpeg_stream_close(stream); - return false; - } - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) - const int find_result = - avformat_find_stream_info(f, NULL); -#else - const int find_result = av_find_stream_info(f); -#endif - if (find_result < 0) { -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&f); -#else - av_close_input_stream(f); -#endif - mpd_ffmpeg_stream_close(stream); - return false; - } - - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag_handler_invoke_duration(handler, handler_ctx, - f->duration / AV_TIME_BASE); - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0) - av_metadata_conv(f, NULL, f->iformat->metadata_conv); -#endif - - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); - int idx = ffmpeg_find_audio_stream(f); - if (idx >= 0) - ffmpeg_scan_dictionary(f->streams[idx]->metadata, - handler, handler_ctx); - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) - avformat_close_input(&f); -#else - av_close_input_stream(f); -#endif - mpd_ffmpeg_stream_close(stream); - - return true; -} - -/** - * A list of extensions found for the formats supported by ffmpeg. - * This list is current as of 02-23-09; To find out if there are more - * supported formats, check the ffmpeg changelog since this date for - * more formats. - */ -static const char *const ffmpeg_suffixes[] = { - "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", - "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", - "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", - "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", - "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", - "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", - "m4a", "m4b", "m4v", - "mad", - "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", - "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", - "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", - "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", - "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", - "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", - "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", - "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", - "wve", - NULL -}; - -static const char *const ffmpeg_mime_types[] = { - "application/flv", - "application/m4a", - "application/mp4", - "application/octet-stream", - "application/ogg", - "application/x-ms-wmz", - "application/x-ms-wmd", - "application/x-ogg", - "application/x-shockwave-flash", - "application/x-shorten", - "audio/8svx", - "audio/16sv", - "audio/aac", - "audio/ac3", - "audio/aiff" - "audio/amr", - "audio/basic", - "audio/flac", - "audio/m4a", - "audio/mp4", - "audio/mpeg", - "audio/musepack", - "audio/ogg", - "audio/qcelp", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-8svx", - "audio/x-16sv", - "audio/x-aac", - "audio/x-ac3", - "audio/x-aiff" - "audio/x-alaw", - "audio/x-au", - "audio/x-dca", - "audio/x-eac3", - "audio/x-flac", - "audio/x-gsm", - "audio/x-mace", - "audio/x-matroska", - "audio/x-monkeys-audio", - "audio/x-mpeg", - "audio/x-ms-wma", - "audio/x-ms-wax", - "audio/x-musepack", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - "audio/x-pn-realaudio", - "audio/x-pn-multirate-realaudio", - "audio/x-speex", - "audio/x-tta" - "audio/x-voc", - "audio/x-wav", - "audio/x-wma", - "audio/x-wv", - "video/anim", - "video/quicktime", - "video/msvideo", - "video/ogg", - "video/theora", - "video/webm", - "video/x-dv", - "video/x-flv", - "video/x-matroska", - "video/x-mjpeg", - "video/x-mpeg", - "video/x-ms-asf", - "video/x-msvideo", - "video/x-ms-wmv", - "video/x-ms-wvx", - "video/x-ms-wm", - "video/x-ms-wmx", - "video/x-nut", - "video/x-pva", - "video/x-theora", - "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 -}; - -const struct decoder_plugin ffmpeg_decoder_plugin = { - .name = "ffmpeg", - .init = ffmpeg_init, - .stream_decode = ffmpeg_decode, - .scan_stream = ffmpeg_scan_stream, - .suffixes = ffmpeg_suffixes, - .mime_types = ffmpeg_mime_types -}; diff --git a/src/decoder/ffmpeg_metadata.c b/src/decoder/ffmpeg_metadata.c deleted file mode 100644 index 3ef774f63..000000000 --- a/src/decoder/ffmpeg_metadata.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ffmpeg_metadata.h" -#include "tag_table.h" -#include "tag_handler.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ffmpeg" - -static const struct tag_table ffmpeg_tags[] = { -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,50,0) - { "author", TAG_ARTIST }, -#endif - { "year", TAG_DATE }, - { "author-sort", TAG_ARTIST_SORT }, - { "album_artist", TAG_ALBUM_ARTIST }, - { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, - - /* sentinel */ - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -ffmpeg_copy_metadata(enum tag_type type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *mt = NULL; - - while ((mt = av_dict_get(m, name, mt, 0)) != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - type, mt->value); -} - -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) - -static void -ffmpeg_scan_pairs(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *i = NULL; - - while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL) - tag_handler_invoke_pair(handler, handler_ctx, - i->key, i->value); -} - -#endif - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(i, dict, tag_item_names[i], - handler, handler_ctx); - - for (const struct tag_table *i = ffmpeg_tags; - i->name != NULL; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); - -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) - if (handler->pair != NULL) - ffmpeg_scan_pairs(dict, handler, handler_ctx); -#endif -} diff --git a/src/decoder/ffmpeg_metadata.h b/src/decoder/ffmpeg_metadata.h deleted file mode 100644 index 60658f479..000000000 --- a/src/decoder/ffmpeg_metadata.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FFMPEG_METADATA_H -#define MPD_FFMPEG_METADATA_H - -#include <libavformat/avformat.h> -#include <libavutil/avutil.h> -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) -#include <libavutil/dict.h> -#endif - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0) -#define AVDictionary AVMetadata -#define AVDictionaryEntry AVMetadataTag -#define av_dict_get av_metadata_get -#endif - -struct tag_handler; - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h deleted file mode 100644 index 9a30acc26..000000000 --- a/src/decoder/flac_compat.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * 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 deleted file mode 100644 index fb0b3502d..000000000 --- a/src/decoder/flac_decoder_plugin.c +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* 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_lock_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_lock_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_lock_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb(status, (struct flac_data *) fdata); -} - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - 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: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } - - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ -static void flacPrintErroredState(FLAC__StreamDecoderState state) -{ - 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: - case FLAC__STREAM_DECODER_SEEK_ERROR: - case FLAC__STREAM_DECODER_ABORTED: - case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__STREAM_DECODER_UNINITIALIZED: - break; - } - - g_warning("%s\n", FLAC__StreamDecoderStateString[state]); -} -#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 bool -flac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - return flac_scan_file2(file, NULL, handler, handler_ctx); -} - -/** - * 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) && - decoder_get_command(decoder) == DECODE_COMMAND_NONE) { - /* a failure that was not triggered by a - decoder command */ - flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); - break; - } - } -} - -static FLAC__StreamDecoderInitStatus -stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - return FLAC__stream_decoder_init_ogg_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, - flac_write_cb, - flacMetadata, - flac_error_cb, - data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif -} - -static FLAC__StreamDecoderInitStatus -stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ - return FLAC__stream_decoder_init_stream(flac_dec, - flac_read_cb, flac_seek_cb, - flac_tell_cb, flac_length_cb, - flac_eof_cb, flac_write_cb, - flacMetadata, - flac_error_cb, - data); -} - -static FLAC__StreamDecoderInitStatus -stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) -{ - return is_ogg - ? stream_init_oggflac(flac_dec, data) - : stream_init_flac(flac_dec, data); -} - -static void -flac_decode_internal(struct decoder * decoder, - struct input_stream *input_stream, - bool is_ogg) -{ - FLAC__StreamDecoder *flac_dec; - struct flac_data data; - - flac_dec = flac_decoder_new(); - if (flac_dec == NULL) - return; - - flac_data_init(&data, decoder, input_stream); - data.tag = tag_new(); - - FLAC__StreamDecoderInitStatus status = - stream_init(flac_dec, &data, is_ogg); - if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif - return; - } - - if (!flac_decoder_initialize(&data, flac_dec, 0)) { - flac_data_deinit(&data); - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - flac_decoder_loop(&data, flac_dec, 0, 0); - - flac_data_deinit(&data); - - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); -} - -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 bool -oggflac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) { - FLAC__metadata_chain_delete(chain); - return false; - } - - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; - - flac_scan_metadata(NULL, block, - handler, handler_ctx); - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); - - FLAC__metadata_chain_delete(chain); - return true; -} - -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_lock_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, - .scan_file = oggflac_scan_file, - .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, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, -}; diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c deleted file mode 100644 index bd1eaf323..000000000 --- a/src/decoder/flac_metadata.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "flac_metadata.h" -#include "replay_gain_info.h" -#include "tag.h" -#include "tag_handler.h" -#include "tag_table.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(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, enum tag_type tag_type, - const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - size_t value_length; - - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { - char *p = g_strndup(value, value_length); - tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); - g_free(p); - return true; - } - - return false; -} - -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -flac_scan_comment(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != NULL) { - char *name = g_strdup((const char*)entry->entry); - char *value = strchr(name, '='); - - if (value != NULL && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) - if (flac_copy_comment(entry, i->name, i->type, char_tnum, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (flac_copy_comment(entry, - tag_item_names[i], i, char_tnum, - handler, handler_ctx)) - return; -} - -static void -flac_scan_comments(const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < comment->num_comments; ++i) - flac_scan_comment(char_tnum, &comment->comments[i], - handler, handler_ctx); -} - -void -flac_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx) -{ - switch (block->type) { - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_scan_comments(track, &block->data.vorbis_comment, - handler, handler_ctx); - break; - - case FLAC__METADATA_TYPE_STREAMINFO: - if (block->data.stream_info.sample_rate > 0) - tag_handler_invoke_duration(handler, handler_ctx, - flac_duration(&block->data.stream_info)); - break; - - default: - break; - } -} - -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment) -{ - flac_scan_comments(char_tnum, comment, - &add_tag_handler, tag); -} - -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx) -{ - 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 false; - } - - do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) - break; - - flac_scan_metadata(char_tnum, block, handler, handler_ctx); - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - return true; -} - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h deleted file mode 100644 index 3c463d5d6..000000000 --- a/src/decoder/flac_metadata.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_METADATA_H -#define MPD_FLAC_METADATA_H - -#include <assert.h> -#include <stdbool.h> -#include <FLAC/metadata.h> - -struct tag_handler; -struct tag; -struct replay_gain_info; - -static inline unsigned -flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - assert(stream_info->sample_rate > 0); - - return (stream_info->total_samples + stream_info->sample_rate - 1) / - stream_info->sample_rate; -} - -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_scan_metadata(const char *track, - const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); - -bool -flac_scan_file2(const char *file, const char *char_tnum, - const struct tag_handler *handler, void *handler_ctx); - -struct tag * -flac_tag_load(const char *file, const char *char_tnum); - -#endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c deleted file mode 100644 index 6964d8ac6..000000000 --- a/src/decoder/flac_pcm.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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_FLOAT: - case SAMPLE_FORMAT_DSD: - case SAMPLE_FORMAT_UNDEFINED: - /* unreachable */ - assert(false); - } -} diff --git a/src/decoder/flac_pcm.h b/src/decoder/flac_pcm.h deleted file mode 100644 index a931998c1..000000000 --- a/src/decoder/flac_pcm.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_PCM_H -#define MPD_FLAC_PCM_H - -#include "audio_format.h" - -#include <FLAC/ordinals.h> - -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); - -#endif diff --git a/src/decoder/fluidsynth_decoder_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c deleted file mode 100644 index 894b2d353..000000000 --- a/src/decoder/fluidsynth_decoder_plugin.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "conf.h" - -#include <glib.h> - -#include <fluidsynth.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "fluidsynth" - -static unsigned sample_rate; -static const char *soundfont_path; - -/** - * Convert a fluidsynth log level to a GLib log level. - */ -static GLogLevelFlags -fluidsynth_level_to_glib(enum fluid_log_level level) -{ - switch (level) { - case FLUID_PANIC: - case FLUID_ERR: - return G_LOG_LEVEL_CRITICAL; - - case FLUID_WARN: - return G_LOG_LEVEL_WARNING; - - case FLUID_INFO: - return G_LOG_LEVEL_INFO; - - case FLUID_DBG: - case LAST_LOG_LEVEL: - return G_LOG_LEVEL_DEBUG; - } - - /* invalid fluidsynth log level */ - return G_LOG_LEVEL_MESSAGE; -} - -/** - * The fluidsynth logging callback. It forwards messages to the GLib - * logging library. - */ -static void -fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data) -{ - g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(level), "%s", message); -} - -static bool -fluidsynth_init(const struct config_param *param) -{ - GError *error = NULL; - - sample_rate = config_get_block_unsigned(param, "sample_rate", 48000); - if (!audio_check_sample_rate(sample_rate, &error)) { - g_warning("%s\n", error->message); - g_error_free(error); - return false; - } - - soundfont_path = - config_get_block_string(param, "soundfont", - "/usr/share/sounds/sf2/FluidR3_GM.sf2"); - - fluid_set_log_function(LAST_LOG_LEVEL, - fluidsynth_mpd_log_function, NULL); - - return true; -} - -static void -fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) -{ - char setting_sample_rate[] = "synth.sample-rate"; - /* - char setting_verbose[] = "synth.verbose"; - char setting_yes[] = "yes"; - */ - fluid_settings_t *settings; - fluid_synth_t *synth; - fluid_player_t *player; - int ret; - enum decoder_command cmd; - - /* set up fluid settings */ - - settings = new_fluid_settings(); - if (settings == NULL) - return; - - fluid_settings_setnum(settings, setting_sample_rate, sample_rate); - - /* - fluid_settings_setstr(settings, setting_verbose, setting_yes); - */ - - /* create the fluid synth */ - - synth = new_fluid_synth(settings); - if (synth == NULL) { - delete_fluid_settings(settings); - return; - } - - ret = fluid_synth_sfload(synth, soundfont_path, true); - if (ret < 0) { - g_warning("fluid_synth_sfload() failed"); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* create the fluid player */ - - player = new_fluid_player(synth); - if (player == NULL) { - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - ret = fluid_player_add(player, path_fs); - if (ret != 0) { - g_warning("fluid_player_add() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* start the player */ - - ret = fluid_player_play(player); - if (ret != 0) { - g_warning("fluid_player_play() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* initialization complete - announce the audio format to the - MPD core */ - - struct audio_format audio_format; - audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2); - decoder_initialized(decoder, &audio_format, false, -1); - - while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { - int16_t buffer[2048]; - const unsigned max_frames = G_N_ELEMENTS(buffer) / 2; - - /* read samples from fluidsynth and send them to the - MPD core */ - - ret = fluid_synth_write_s16(synth, max_frames, - buffer, 0, 2, - buffer, 1, 2); - if (ret != 0) - break; - - cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer), - 0); - if (cmd != DECODE_COMMAND_NONE) - break; - } - - /* clean up */ - - fluid_player_stop(player); - fluid_player_join(player); - - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); -} - -static bool -fluidsynth_scan_file(const char *file, - G_GNUC_UNUSED const struct tag_handler *handler, - G_GNUC_UNUSED void *handler_ctx) -{ - return fluid_is_midifile(file); -} - -static const char *const fluidsynth_suffixes[] = { - "mid", - NULL -}; - -const struct decoder_plugin fluidsynth_decoder_plugin = { - .name = "fluidsynth", - .init = fluidsynth_init, - .file_decode = fluidsynth_file_decode, - .scan_file = fluidsynth_scan_file, - .suffixes = fluidsynth_suffixes, -}; diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c deleted file mode 100644 index 237a1deb1..000000000 --- a/src/decoder/gme_decoder_plugin.c +++ /dev/null @@ -1,257 +0,0 @@ -#include "config.h" -#include "../decoder_api.h" -#include "audio_check.h" -#include "uri.h" -#include "tag_handler.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 bool -gme_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - 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 false; - } - if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){ - g_warning("%s", gme_err); - gme_delete(emu); - return false; - } - - assert(ti != NULL); - - if(ti->length > 0) - tag_handler_invoke_duration(handler, handler_ctx, - ti->length / 100); - - if(ti->song != NULL){ - if(gme_track_count(emu) > 1){ - /* start numbering subtunes from 1 */ - char *tag_title=g_strdup_printf("%s (%d/%d)", - ti->song, song_num+1, gme_track_count(emu)); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, tag_title); - g_free(tag_title); - }else - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, ti->song); - } - if(ti->author != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, ti->author); - if(ti->game != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ALBUM, ti->game); - if(ti->comment != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_COMMENT, ti->comment); - if(ti->copyright != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, ti->copyright); - - gme_free_info(ti); - gme_delete(emu); - - return true; -} - -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, - .scan_file = gme_scan_file, - .suffixes = gme_suffixes, - .container_scan = gme_container_scan, -}; diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c deleted file mode 100644 index 62c371642..000000000 --- a/src/decoder/mad_decoder_plugin.c +++ /dev/null @@ -1,1203 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "conf.h" -#include "tag_id3.h" -#include "tag_rva2.h" -#include "tag_handler.h" -#include "audio_check.h" - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> -#include <stdio.h> -#include <glib.h> -#include <mad.h> - -#ifdef HAVE_ID3TAG -#include <id3tag.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mad" - -#define FRAMES_CUSHION 2000 - -#define READ_BUFFER_SIZE 40960 - -enum mp3_action { - DECODE_SKIP = -3, - DECODE_BREAK = -2, - DECODE_CONT = -1, - DECODE_OK = 0 -}; - -enum muteframe { - MUTEFRAME_NONE, - MUTEFRAME_SKIP, - MUTEFRAME_SEEK -}; - -/* the number of samples of silence the decoder inserts at start */ -#define DECODERDELAY 529 - -#define DEFAULT_GAPLESS_MP3_PLAYBACK true - -static bool gapless_playback; - -static inline int32_t -mad_fixed_to_24_sample(mad_fixed_t sample) -{ - enum { - bits = 24, - MIN = -MAD_F_ONE, - MAX = MAD_F_ONE - 1 - }; - - /* round */ - sample = sample + (1L << (MAD_F_FRACBITS - bits)); - - /* clip */ - if (sample > MAX) - sample = MAX; - else if (sample < MIN) - sample = MIN; - - /* quantize */ - return sample >> (MAD_F_FRACBITS + 1 - bits); -} - -static void -mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, - unsigned int start, unsigned int end, - unsigned int num_channels) -{ - unsigned int i, c; - - for (i = start; i < end; ++i) { - for (c = 0; c < num_channels; ++c) - *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); - } -} - -static bool -mp3_plugin_init(G_GNUC_UNUSED const struct config_param *param) -{ - gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, - DEFAULT_GAPLESS_MP3_PLAYBACK); - return true; -} - -#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 - -struct mp3_data { - struct mad_stream stream; - struct mad_frame frame; - struct mad_synth synth; - mad_timer_t timer; - unsigned char input_buffer[READ_BUFFER_SIZE]; - int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; - float total_time; - float elapsed_time; - float seek_where; - enum muteframe mute_frame; - long *frame_offsets; - mad_timer_t *times; - unsigned long highest_frame; - unsigned long max_frames; - unsigned long current_frame; - unsigned int drop_start_frames; - 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; - unsigned long bit_rate; - struct decoder *decoder; - struct input_stream *input_stream; - enum mad_layer layer; -}; - -static void -mp3_data_init(struct mp3_data *data, struct decoder *decoder, - struct input_stream *input_stream) -{ - data->mute_frame = MUTEFRAME_NONE; - data->highest_frame = 0; - data->max_frames = 0; - data->frame_offsets = NULL; - data->times = NULL; - data->current_frame = 0; - data->drop_start_frames = 0; - 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; - data->decoder = decoder; - data->input_stream = input_stream; - data->layer = 0; - - mad_stream_init(&data->stream); - mad_stream_options(&data->stream, MAD_OPTION_IGNORECRC); - mad_frame_init(&data->frame); - mad_synth_init(&data->synth); - mad_timer_reset(&data->timer); -} - -static bool mp3_seek(struct mp3_data *data, long offset) -{ - if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, NULL)) - return false; - - mad_stream_buffer(&data->stream, data->input_buffer, 0); - (data->stream).error = 0; - - return true; -} - -static bool -mp3_fill_buffer(struct mp3_data *data) -{ - size_t remaining, length; - unsigned char *dest; - - if (data->stream.next_frame != NULL) { - remaining = data->stream.bufend - data->stream.next_frame; - memmove(data->input_buffer, data->stream.next_frame, - remaining); - dest = (data->input_buffer) + remaining; - length = READ_BUFFER_SIZE - remaining; - } else { - remaining = 0; - length = READ_BUFFER_SIZE; - dest = data->input_buffer; - } - - /* we've exhausted the read buffer, so give up!, these potential - * mp3 frames are way too big, and thus unlikely to be mp3 frames */ - if (length == 0) - return false; - - length = decoder_read(data->decoder, data->input_stream, dest, length); - if (length == 0) - return false; - - mad_stream_buffer(&data->stream, data->input_buffer, - length + remaining); - (data->stream).error = 0; - - return true; -} - -#ifdef HAVE_ID3TAG -static bool -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; - - replay_gain_info_init(replay_gain_info); - - 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, "replaygain_track_gain") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = atof(value); - found = true; - } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value); - found = true; - } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = atof(value); - found = true; - } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value); - found = true; - } - - free(key); - free(value); - } - - return found || - /* fall back on RVA2 if no replaygain tags found */ - 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); - } - - return found; -} -#endif - -static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, - struct tag **mpd_tag) -{ -#ifdef HAVE_ID3TAG - struct id3_tag *id3_tag = NULL; - id3_length_t count; - id3_byte_t const *id3_data; - id3_byte_t *allocated = NULL; - - count = data->stream.bufend - data->stream.this_frame; - - if (tagsize <= count) { - id3_data = data->stream.this_frame; - mad_stream_skip(&(data->stream), tagsize); - } else { - allocated = g_malloc(tagsize); - memcpy(allocated, data->stream.this_frame, count); - mad_stream_skip(&(data->stream), count); - - while (count < tagsize) { - size_t len; - - len = decoder_read(data->decoder, data->input_stream, - allocated + count, tagsize - count); - if (len == 0) - break; - else - count += len; - } - - if (count != tagsize) { - g_debug("error parsing ID3 tag"); - g_free(allocated); - return; - } - - id3_data = allocated; - } - - id3_tag = id3_tag_parse(id3_data, tagsize); - if (id3_tag == NULL) { - g_free(allocated); - return; - } - - if (mpd_tag) { - struct tag *tmp_tag = tag_id3_import(id3_tag); - if (tmp_tag != NULL) { - if (*mpd_tag != NULL) - tag_free(*mpd_tag); - *mpd_tag = tmp_tag; - } - } - - 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)) - decoder_mixramp(data->decoder, replay_gain_db, - mixramp_start, mixramp_end); - } - - id3_tag_delete(id3_tag); - - g_free(allocated); -#else /* !HAVE_ID3TAG */ - (void)mpd_tag; - - /* This code is enabled when libid3tag is disabled. Instead - of parsing the ID3 frame, it just skips it. */ - - size_t count = data->stream.bufend - data->stream.this_frame; - - if (tagsize <= count) { - mad_stream_skip(&data->stream, tagsize); - } else { - mad_stream_skip(&data->stream, count); - - while (count < tagsize) { - size_t len = tagsize - count; - char ignored[1024]; - if (len > sizeof(ignored)) - len = sizeof(ignored); - - len = decoder_read(data->decoder, data->input_stream, - ignored, len); - if (len == 0) - break; - else - count += len; - } - } -#endif -} - -#ifndef HAVE_ID3TAG -/** - * This function emulates libid3tag when it is disabled. Instead of - * doing a real analyzation of the frame, it just checks whether the - * frame begins with the string "ID3". If so, it returns the length - * of the ID3 frame. - */ -static signed long -id3_tag_query(const void *p0, size_t length) -{ - const char *p = p0; - - return length >= 10 && memcmp(p, "ID3", 3) == 0 - ? (p[8] << 7) + p[9] + 10 - : 0; -} -#endif /* !HAVE_ID3TAG */ - -static enum mp3_action -decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag) -{ - enum mad_layer layer; - - if ((data->stream).buffer == NULL - || (data->stream).error == MAD_ERROR_BUFLEN) { - if (!mp3_fill_buffer(data)) - return DECODE_BREAK; - } - if (mad_header_decode(&data->frame.header, &data->stream)) { - if ((data->stream).error == MAD_ERROR_LOSTSYNC && - (data->stream).this_frame) { - signed long tagsize = id3_tag_query((data->stream). - this_frame, - (data->stream). - bufend - - (data->stream). - this_frame); - - if (tagsize > 0) { - if (tag && !(*tag)) { - mp3_parse_id3(data, (size_t)tagsize, - tag); - } else { - mad_stream_skip(&(data->stream), - tagsize); - } - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE((data->stream).error)) { - return DECODE_SKIP; - } else { - if ((data->stream).error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - g_warning("unrecoverable frame level error " - "(%s).\n", - mad_stream_errorstr(&data->stream)); - return DECODE_BREAK; - } - } - } - - layer = data->frame.header.layer; - if (!data->layer) { - if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) { - /* Only layer 2 and 3 have been tested to work */ - return DECODE_SKIP; - } - data->layer = layer; - } else if (layer != data->layer) { - /* Don't decode frames with a different layer than the first */ - return DECODE_SKIP; - } - - return DECODE_OK; -} - -static enum mp3_action -decodeNextFrame(struct mp3_data *data) -{ - if ((data->stream).buffer == NULL - || (data->stream).error == MAD_ERROR_BUFLEN) { - if (!mp3_fill_buffer(data)) - return DECODE_BREAK; - } - if (mad_frame_decode(&data->frame, &data->stream)) { - if ((data->stream).error == MAD_ERROR_LOSTSYNC) { - signed long tagsize = id3_tag_query((data->stream). - this_frame, - (data->stream). - bufend - - (data->stream). - this_frame); - if (tagsize > 0) { - mad_stream_skip(&(data->stream), tagsize); - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE((data->stream).error)) { - return DECODE_SKIP; - } else { - if ((data->stream).error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - g_warning("unrecoverable frame level error " - "(%s).\n", - mad_stream_errorstr(&data->stream)); - return DECODE_BREAK; - } - } - } - - return DECODE_OK; -} - -/* xing stuff stolen from alsaplayer, and heavily modified by jat */ -#define XI_MAGIC (('X' << 8) | 'i') -#define NG_MAGIC (('n' << 8) | 'g') -#define IN_MAGIC (('I' << 8) | 'n') -#define FO_MAGIC (('f' << 8) | 'o') - -enum xing_magic { - XING_MAGIC_XING, /* VBR */ - XING_MAGIC_INFO /* CBR */ -}; - -struct xing { - long flags; /* valid fields (see below) */ - unsigned long frames; /* total number of frames */ - unsigned long bytes; /* total number of bytes */ - unsigned char toc[100]; /* 100-point seek table */ - long scale; /* VBR quality */ - enum xing_magic magic; /* header magic */ -}; - -enum { - XING_FRAMES = 0x00000001L, - XING_BYTES = 0x00000002L, - XING_TOC = 0x00000004L, - XING_SCALE = 0x00000008L -}; - -struct lame_version { - unsigned major; - unsigned minor; -}; - -struct lame { - char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ - 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 */ - int encoder_delay; /* # of added samples at start of mp3 */ - int encoder_padding; /* # of added samples at end of mp3 */ - int crc; /* CRC of the first 190 bytes of this frame */ -}; - -static bool -parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) -{ - unsigned long bits; - int bitlen; - int bitsleft; - int i; - - bitlen = *oldbitlen; - - if (bitlen < 16) - return false; - - bits = mad_bit_read(ptr, 16); - bitlen -= 16; - - if (bits == XI_MAGIC) { - if (bitlen < 16) - return false; - - if (mad_bit_read(ptr, 16) != NG_MAGIC) - return false; - - bitlen -= 16; - xing->magic = XING_MAGIC_XING; - } else if (bits == IN_MAGIC) { - if (bitlen < 16) - return false; - - if (mad_bit_read(ptr, 16) != FO_MAGIC) - return false; - - bitlen -= 16; - xing->magic = XING_MAGIC_INFO; - } - else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; - else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; - else - return false; - - if (bitlen < 32) - return false; - xing->flags = mad_bit_read(ptr, 32); - bitlen -= 32; - - if (xing->flags & XING_FRAMES) { - if (bitlen < 32) - return false; - xing->frames = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - if (xing->flags & XING_BYTES) { - if (bitlen < 32) - return false; - xing->bytes = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - if (xing->flags & XING_TOC) { - if (bitlen < 800) - return false; - for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); - bitlen -= 800; - } - - if (xing->flags & XING_SCALE) { - if (bitlen < 32) - return false; - xing->scale = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - /* Make sure we consume no less than 120 bytes (960 bits) in hopes that - * the LAME tag is found there, and not right after the Xing header */ - bitsleft = 960 - ((*oldbitlen) - bitlen); - if (bitsleft < 0) - return false; - else if (bitsleft > 0) { - mad_bit_read(ptr, bitsleft); - bitlen -= bitsleft; - } - - *oldbitlen = bitlen; - - return true; -} - -static bool -parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) -{ - int adj = 0; - int name; - int orig; - int sign; - int gain; - int i; - - /* Unlike the xing header, the lame tag has a fixed length. Fail if - * not all 36 bytes (288 bits) are there. */ - if (*bitlen < 288) - return false; - - for (i = 0; i < 9; i++) - lame->encoder[i] = (char)mad_bit_read(ptr, 8); - lame->encoder[9] = '\0'; - - *bitlen -= 72; - - /* This is technically incorrect, since the encoder might not be lame. - * But there's no other way to determine if this is a lame tag, and we - * wouldn't want to go reading a tag that's not there. */ - if (!g_str_has_prefix(lame->encoder, "LAME")) - return false; - - if (sscanf(lame->encoder+4, "%u.%u", - &lame->version.major, &lame->version.minor) != 2) - return false; - - g_debug("detected LAME version %i.%i (\"%s\")\n", - lame->version.major, lame->version.minor, lame->encoder); - - /* The reference volume was changed from the 83dB used in the - * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older - * versions, since everyone else uses 89dB instead of 83dB. - * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so - * it's impossible to make the proper adjustment for 3.95. - * Fortunately, 3.95 was only out for about a day before 3.95.1 was - * released. -- tmz */ - if (lame->version.major < 3 || - (lame->version.major == 3 && lame->version.minor < 95)) - adj = 6; - - mad_bit_read(ptr, 16); - - lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ - g_debug("LAME peak found: %f\n", lame->peak); - - lame->track_gain = 0; - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 1 && orig != 0) { - lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; - g_debug("LAME track gain found: %f\n", lame->track_gain); - } - - /* tmz reports that this isn't currently written by any version of lame - * (as of 3.97). Since we have no way of testing it, don't use it. - * Wouldn't want to go blowing someone's ears just because we read it - * wrong. :P -- jat */ - lame->album_gain = 0; -#if 0 - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 2 && orig != 0) { - lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj; - g_debug("LAME album gain found: %f\n", lame->track_gain); - } -#else - mad_bit_read(ptr, 16); -#endif - - mad_bit_read(ptr, 16); - - lame->encoder_delay = mad_bit_read(ptr, 12); - lame->encoder_padding = mad_bit_read(ptr, 12); - - g_debug("encoder delay is %i, encoder padding is %i\n", - lame->encoder_delay, lame->encoder_padding); - - mad_bit_read(ptr, 80); - - lame->crc = mad_bit_read(ptr, 16); - - *bitlen -= 216; - - return true; -} - -static inline float -mp3_frame_duration(const struct mad_frame *frame) -{ - return mad_timer_count(frame->header.duration, - MAD_UNITS_MILLISECONDS) / 1000.0; -} - -static goffset -mp3_this_frame_offset(const struct mp3_data *data) -{ - goffset offset = data->input_stream->offset; - - if (data->stream.this_frame != NULL) - offset -= data->stream.bufend - data->stream.this_frame; - else - offset -= data->stream.bufend - data->stream.buffer; - - return offset; -} - -static goffset -mp3_rest_including_this_frame(const struct mp3_data *data) -{ - return data->input_stream->size - mp3_this_frame_offset(data); -} - -/** - * Attempt to calulcate the length of the song from filesize - */ -static void -mp3_filesize_to_song_length(struct mp3_data *data) -{ - goffset rest = mp3_rest_including_this_frame(data); - - if (rest > 0) { - float frame_duration = mp3_frame_duration(&data->frame); - - data->total_time = (rest * 8.0) / (data->frame).header.bitrate; - data->max_frames = data->total_time / frame_duration + - FRAMES_CUSHION; - } else { - data->max_frames = FRAMES_CUSHION; - data->total_time = 0; - } -} - -static bool -mp3_decode_first_frame(struct mp3_data *data, struct tag **tag) -{ - struct xing xing; - struct lame lame; - struct mad_bitptr ptr; - int bitlen; - enum mp3_action ret; - - /* stfu gcc */ - memset(&xing, 0, sizeof(struct xing)); - xing.flags = 0; - - while (true) { - do { - ret = decode_next_frame_header(data, tag); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_SKIP) continue; - - do { - ret = decodeNextFrame(data); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_OK) break; - } - - ptr = data->stream.anc_ptr; - bitlen = data->stream.anc_bitlen; - - mp3_filesize_to_song_length(data); - - /* - * if an xing tag exists, use that! - */ - if (parse_xing(&xing, &ptr, &bitlen)) { - data->found_xing = true; - data->mute_frame = MUTEFRAME_SKIP; - - if ((xing.flags & XING_FRAMES) && xing.frames) { - mad_timer_t duration = data->frame.header.duration; - mad_timer_multiply(&duration, xing.frames); - data->total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; - data->max_frames = xing.frames; - } - - if (parse_lame(&lame, &ptr, &bitlen)) { - if (gapless_playback && - data->input_stream->seekable) { - data->drop_start_samples = lame.encoder_delay + - DECODERDELAY; - data->drop_end_samples = lame.encoder_padding; - } - - /* Album gain isn't currently used. See comment in - * parse_lame() for details. -- jat */ - if (data->decoder != NULL && - !data->found_replay_gain && - lame.track_gain) { - 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; - - if (data->max_frames > 8 * 1024 * 1024) { - g_warning("mp3 file header indicates too many frames: %lu\n", - data->max_frames); - return false; - } - - data->frame_offsets = g_malloc(sizeof(long) * data->max_frames); - data->times = g_malloc(sizeof(mad_timer_t) * data->max_frames); - - return true; -} - -static void mp3_data_finish(struct mp3_data *data) -{ - mad_synth_finish(&data->synth); - mad_frame_finish(&data->frame); - mad_stream_finish(&data->stream); - - g_free(data->frame_offsets); - g_free(data->times); -} - -/* this is primarily used for getting total time for tags */ -static int -mad_decoder_total_file_time(struct input_stream *is) -{ - struct mp3_data data; - int ret; - - 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); - - return ret; -} - -static bool -mp3_open(struct input_stream *is, struct mp3_data *data, - struct decoder *decoder, struct tag **tag) -{ - mp3_data_init(data, decoder, is); - *tag = NULL; - if (!mp3_decode_first_frame(data, tag)) { - mp3_data_finish(data); - if (tag && *tag) - tag_free(*tag); - return false; - } - - return true; -} - -static long -mp3_time_to_frame(const struct mp3_data *data, double t) -{ - unsigned long i; - - for (i = 0; i < data->highest_frame; ++i) { - double frame_time = - mad_timer_count(data->times[i], - MAD_UNITS_MILLISECONDS) / 1000.; - if (frame_time >= t) - break; - } - - return i; -} - -static void -mp3_update_timer_next_frame(struct mp3_data *data) -{ - if (data->current_frame >= data->highest_frame) { - /* record this frame's properties in - data->frame_offsets (for seeking) and - data->times */ - data->bit_rate = (data->frame).header.bitrate; - - if (data->current_frame >= data->max_frames) - /* cap data->current_frame */ - data->current_frame = data->max_frames - 1; - else - data->highest_frame++; - - data->frame_offsets[data->current_frame] = - mp3_this_frame_offset(data); - - mad_timer_add(&data->timer, (data->frame).header.duration); - data->times[data->current_frame] = data->timer; - } else - /* get the new timer value from data->times */ - data->timer = data->times[data->current_frame]; - - data->current_frame++; - data->elapsed_time = - mad_timer_count(data->timer, MAD_UNITS_MILLISECONDS) / 1000.0; -} - -/** - * Sends the synthesized current frame via decoder_data(). - */ -static enum decoder_command -mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length) -{ - unsigned max_samples; - - max_samples = sizeof(data->output_buffer) / - sizeof(data->output_buffer[0]) / - MAD_NCHANNELS(&(data->frame).header); - - while (i < pcm_length) { - enum decoder_command cmd; - unsigned int num_samples = pcm_length - i; - if (num_samples > max_samples) - num_samples = max_samples; - - i += num_samples; - - mad_fixed_to_24_buffer(data->output_buffer, - &data->synth, - i - num_samples, i, - MAD_NCHANNELS(&(data->frame).header)); - num_samples *= MAD_NCHANNELS(&(data->frame).header); - - cmd = decoder_data(data->decoder, data->input_stream, - data->output_buffer, - sizeof(data->output_buffer[0]) * num_samples, - data->bit_rate / 1000); - if (cmd != DECODE_COMMAND_NONE) - return cmd; - } - - return DECODE_COMMAND_NONE; -} - -/** - * Synthesize the current frame and send it via decoder_data(). - */ -static enum decoder_command -mp3_synth_and_send(struct mp3_data *data) -{ - unsigned i, pcm_length; - enum decoder_command cmd; - - mad_synth_frame(&data->synth, &data->frame); - - if (!data->found_first_frame) { - unsigned int samples_per_frame = data->synth.pcm.length; - data->drop_start_frames = data->drop_start_samples / samples_per_frame; - data->drop_end_frames = data->drop_end_samples / samples_per_frame; - data->drop_start_samples = data->drop_start_samples % samples_per_frame; - data->drop_end_samples = data->drop_end_samples % samples_per_frame; - data->found_first_frame = true; - } - - if (data->drop_start_frames > 0) { - data->drop_start_frames--; - return DECODE_COMMAND_NONE; - } else if ((data->drop_end_frames > 0) && - (data->current_frame == (data->max_frames + 1 - data->drop_end_frames))) { - /* stop decoding, effectively dropping all remaining - frames */ - return DECODE_COMMAND_STOP; - } - - if (!data->decoded_first_frame) { - i = data->drop_start_samples; - data->decoded_first_frame = true; - } else - i = 0; - - pcm_length = data->synth.pcm.length; - if (data->drop_end_samples && - (data->current_frame == data->max_frames - data->drop_end_frames)) { - if (data->drop_end_samples >= pcm_length) - pcm_length = 0; - else - pcm_length -= data->drop_end_samples; - } - - cmd = mp3_send_pcm(data, i, pcm_length); - if (cmd != DECODE_COMMAND_NONE) - return cmd; - - if (data->drop_end_samples && - (data->current_frame == data->max_frames - data->drop_end_frames)) - /* stop decoding, effectively dropping - * all remaining samples */ - return DECODE_COMMAND_STOP; - - return DECODE_COMMAND_NONE; -} - -static bool -mp3_read(struct mp3_data *data) -{ - struct decoder *decoder = data->decoder; - enum mp3_action ret; - enum decoder_command cmd; - - mp3_update_timer_next_frame(data); - - switch (data->mute_frame) { - case MUTEFRAME_SKIP: - data->mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_SEEK: - if (data->elapsed_time >= data->seek_where) - data->mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_NONE: - cmd = mp3_synth_and_send(data); - if (cmd == DECODE_COMMAND_SEEK) { - unsigned long j; - - assert(data->input_stream->seekable); - - j = mp3_time_to_frame(data, - decoder_seek_where(decoder)); - if (j < data->highest_frame) { - if (mp3_seek(data, data->frame_offsets[j])) { - data->current_frame = j; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else { - data->seek_where = decoder_seek_where(decoder); - data->mute_frame = MUTEFRAME_SEEK; - decoder_command_finished(decoder); - } - } else if (cmd != DECODE_COMMAND_NONE) - return false; - } - - while (true) { - bool skip = false; - - do { - struct tag *tag = NULL; - - ret = decode_next_frame_header(data, &tag); - - if (tag != NULL) { - decoder_tag(decoder, data->input_stream, tag); - tag_free(tag); - } - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - else if (ret == DECODE_SKIP) - skip = true; - - if (data->mute_frame == MUTEFRAME_NONE) { - do { - ret = decodeNextFrame(data); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - } - - if (!skip && ret == DECODE_OK) - break; - } - - return ret != DECODE_BREAK; -} - -static void -mp3_decode(struct decoder *decoder, struct input_stream *input_stream) -{ - struct mp3_data data; - GError *error = NULL; - struct tag *tag = NULL; - struct audio_format audio_format; - - 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; - } - - 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); - - if (tag != NULL) { - decoder_tag(decoder, input_stream, tag); - tag_free(tag); - } - - while (mp3_read(&data)) ; - - mp3_data_finish(&data); -} - -static bool -mad_decoder_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - int total_time; - - total_time = mad_decoder_total_file_time(is); - if (total_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const mp3_suffixes[] = { "mp3", "mp2", NULL }; -static const char *const mp3_mime_types[] = { "audio/mpeg", NULL }; - -const struct decoder_plugin mad_decoder_plugin = { - .name = "mad", - .init = mp3_plugin_init, - .stream_decode = mp3_decode, - .scan_stream = mad_decoder_scan_stream, - .suffixes = mp3_suffixes, - .mime_types = mp3_mime_types -}; diff --git a/src/decoder/mikmod_decoder_plugin.c b/src/decoder/mikmod_decoder_plugin.c deleted file mode 100644 index a8fe818de..000000000 --- a/src/decoder/mikmod_decoder_plugin.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "mpd_error.h" -#include "tag_handler.h" - -#include <glib.h> -#include <mikmod.h> -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mikmod" - -/* this is largely copied from alsaplayer */ - -#define MIKMOD_FRAME_SIZE 4096 - -static BOOL -mikmod_mpd_init(void) -{ - return VC_Init(); -} - -static void -mikmod_mpd_exit(void) -{ - VC_Exit(); -} - -static void -mikmod_mpd_update(void) -{ -} - -static BOOL -mikmod_mpd_is_present(void) -{ - return true; -} - -static char drv_name[] = PACKAGE_NAME; -static char drv_version[] = VERSION; - -#if (LIBMIKMOD_VERSION > 0x030106) -static char drv_alias[] = PACKAGE; -#endif - -static MDRIVER drv_mpd = { - NULL, - drv_name, - drv_version, - 0, - 255, -#if (LIBMIKMOD_VERSION > 0x030106) - drv_alias, -#if (LIBMIKMOD_VERSION >= 0x030200) - NULL, /* CmdLineHelp */ -#endif - NULL, /* CommandLine */ -#endif - mikmod_mpd_is_present, - VC_SampleLoad, - VC_SampleUnload, - VC_SampleSpace, - VC_SampleLength, - mikmod_mpd_init, - mikmod_mpd_exit, - NULL, - VC_SetNumVoices, - VC_PlayStart, - VC_PlayStop, - mikmod_mpd_update, - NULL, - VC_VoiceSetVolume, - VC_VoiceGetVolume, - VC_VoiceSetFrequency, - VC_VoiceGetFrequency, - VC_VoiceSetPanning, - VC_VoiceGetPanning, - VC_VoicePlay, - VC_VoiceStop, - VC_VoiceStopped, - VC_VoiceGetPosition, - VC_VoiceRealVolume -}; - -static unsigned mikmod_sample_rate; - -static bool -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; - - MikMod_RegisterDriver(&drv_mpd); - MikMod_RegisterAllLoaders(); - - md_pansep = 64; - md_mixfreq = mikmod_sample_rate; - md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | - DMODE_16BITS); - - if (MikMod_Init(params)) { - g_warning("Could not init MikMod: %s\n", - MikMod_strerror(MikMod_errno)); - return false; - } - - return true; -} - -static void -mikmod_decoder_finish(void) -{ - MikMod_Exit(); -} - -static void -mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) -{ - char *path2; - MODULE *handle; - struct audio_format audio_format; - int ret; - SBYTE buffer[MIKMOD_FRAME_SIZE]; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - 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; - } - - /* Prevent module from looping forever */ - handle->loop = 0; - - 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(buffer, sizeof(buffer)); - cmd = decoder_data(decoder, NULL, buffer, ret, 0); - } - - Player_Stop(); - Player_Free(handle); -} - -static bool -mikmod_decoder_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - char *path2 = g_strdup(path_fs); - MODULE *handle = Player_Load(path2, 128, 0); - - if (handle == NULL) { - g_free(path2); - g_debug("Failed to open file: %s", path_fs); - return false; - - } - - Player_Free(handle); - - char *title = Player_LoadTitle(path2); - g_free(path2); - - if (title != NULL) { - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, title); -#if (LIBMIKMOD_VERSION >= 0x030200) - MikMod_free(title); -#else - free(title); -#endif - } - - return true; -} - -static const char *const mikmod_decoder_suffixes[] = { - "amf", - "dsm", - "far", - "gdm", - "imf", - "it", - "med", - "mod", - "mtm", - "s3m", - "stm", - "stx", - "ult", - "uni", - "xm", - NULL -}; - -const struct decoder_plugin mikmod_decoder_plugin = { - .name = "mikmod", - .init = mikmod_decoder_init, - .finish = mikmod_decoder_finish, - .file_decode = mikmod_decoder_file_decode, - .scan_file = mikmod_decoder_scan_file, - .suffixes = mikmod_decoder_suffixes, -}; diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c deleted file mode 100644 index 5ae4b1a04..000000000 --- a/src/decoder/modplug_decoder_plugin.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "tag_handler.h" - -#include <glib.h> -#include <libmodplug/modplug.h> -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "modplug" - -enum { - MODPLUG_FRAME_SIZE = 4096, - MODPLUG_PREALLOC_BLOCK = 256 * 1024, - MODPLUG_READ_BLOCK = 128 * 1024, - MODPLUG_FILE_LIMIT = 100 * 1024 * 1024, -}; - -static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is) -{ - unsigned char *data; - GByteArray *bdatas; - size_t ret; - - if (is->size == 0) { - g_warning("file is empty"); - return NULL; - } - - if (is->size > MODPLUG_FILE_LIMIT) { - g_warning("file too large"); - return NULL; - } - - //known/unknown size, preallocate array, lets read in chunks - if (is->size > 0) { - bdatas = g_byte_array_sized_new(is->size); - } else { - bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK); - } - - data = g_malloc(MODPLUG_READ_BLOCK); - - while (true) { - ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK); - if (ret == 0) { - if (input_stream_lock_eof(is)) - /* end of file */ - break; - - /* I/O error - skip this song */ - g_free(data); - g_byte_array_free(bdatas, true); - return NULL; - } - - if (bdatas->len + ret > MODPLUG_FILE_LIMIT) { - g_warning("stream too large\n"); - g_free(data); - g_byte_array_free(bdatas, TRUE); - return NULL; - } - - g_byte_array_append(bdatas, data, ret); - } - - g_free(data); - - return bdatas; -} - -static void -mod_decode(struct decoder *decoder, struct input_stream *is) -{ - ModPlugFile *f; - ModPlug_Settings settings; - GByteArray *bdatas; - struct audio_format audio_format; - int ret; - char audio_buffer[MODPLUG_FRAME_SIZE]; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - bdatas = mod_loadfile(decoder, is); - - if (!bdatas) { - g_warning("could not load stream\n"); - return; - } - - ModPlug_GetSettings(&settings); - /* alter setting */ - settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ - settings.mChannels = 2; - settings.mBits = 16; - settings.mFrequency = 44100; - /* insert more setting changes here */ - ModPlug_SetSettings(&settings); - - f = ModPlug_Load(bdatas->data, bdatas->len); - g_byte_array_free(bdatas, TRUE); - if (!f) { - g_warning("could not decode stream\n"); - return; - } - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - assert(audio_format_valid(&audio_format)); - - decoder_initialized(decoder, &audio_format, - is->seekable, ModPlug_GetLength(f) / 1000.0); - - do { - ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, NULL, - audio_buffer, ret, - 0); - - if (cmd == DECODE_COMMAND_SEEK) { - float where = decoder_seek_where(decoder); - - ModPlug_Seek(f, (int)(where * 1000.0)); - - decoder_command_finished(decoder); - } - - } while (cmd != DECODE_COMMAND_STOP); - - ModPlug_Unload(f); -} - -static bool -modplug_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - ModPlugFile *f; - GByteArray *bdatas; - - bdatas = mod_loadfile(NULL, is); - if (!bdatas) - return false; - - f = ModPlug_Load(bdatas->data, bdatas->len); - g_byte_array_free(bdatas, TRUE); - if (f == NULL) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - ModPlug_GetLength(f) / 1000); - - const char *title = ModPlug_GetName(f); - if (title != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, title); - - ModPlug_Unload(f); - - return true; -} - -static const char *const mod_suffixes[] = { - "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it", - "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm", - "ult", "umx", "xm", - NULL -}; - -const struct decoder_plugin modplug_decoder_plugin = { - .name = "modplug", - .stream_decode = mod_decode, - .scan_stream = modplug_scan_stream, - .suffixes = mod_suffixes, -}; diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c deleted file mode 100644 index ca78a22d0..000000000 --- a/src/decoder/mp4ff_decoder_plugin.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_table.h" -#include "tag_handler.h" - -#include <glib.h> - -#include <mp4ff.h> -#include <faad.h> - -#include <assert.h> -#include <stdlib.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mp4ff" - -/* all code here is either based on or copied from FAAD2's frontend code */ - -struct mp4ff_input_stream { - mp4ff_callback_t callback; - - struct decoder *decoder; - struct input_stream *input_stream; -}; - -static int -mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, - uint32_t *sample_rate, unsigned char *channels_r) -{ -#ifdef HAVE_FAAD_LONG - /* 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*)sample_rate; -#else - uint32_t *sample_rate_r = sample_rate; -#endif - int i, rc; - int num_tracks = mp4ff_total_tracks(infile); - - for (i = 0; i < num_tracks; i++) { - unsigned char *buff = NULL; - unsigned int buff_size = 0; - - if (mp4ff_get_track_type(infile, i) != 1) - /* not an audio track */ - continue; - - if (decoder == NULL) - /* have don't have a decoder to initialize - - we're done now, because we found an audio - track */ - return i; - - mp4ff_get_decoder_config(infile, i, &buff, &buff_size); - if (buff == NULL) - continue; - - rc = faacDecInit2(decoder, buff, buff_size, - sample_rate_r, channels_r); - free(buff); - - if (rc >= 0) - /* found a valid AAC track */ - return i; - } - - /* can't decode this */ - return -1; -} - -static uint32_t -mp4_read(void *user_data, void *buffer, uint32_t length) -{ - struct mp4ff_input_stream *mis = user_data; - - if (length == 0) - /* libmp4ff is known to attempt to read 0 bytes - make - this a special case, because the input_stream API - would not allow this */ - return 0; - - return decoder_read(mis->decoder, mis->input_stream, buffer, length); -} - -static uint32_t -mp4_seek(void *user_data, uint64_t position) -{ - struct mp4ff_input_stream *mis = user_data; - - return input_stream_lock_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) -{ - faacDecHandle decoder; - faacDecConfigurationPtr config; - int track; - uint32_t sample_rate; - unsigned char channels; - GError *error = NULL; - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX - config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR - config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); - - track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels); - if (track < 0) { - g_warning("No AAC track found"); - faacDecClose(decoder); - return NULL; - } - - 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 mp4ff_input_stream mis; - mp4ff_t *mp4fh; - int32_t track; - float file_time, total_time; - int32_t scale; - faacDecHandle decoder; - struct audio_format audio_format; - faacDecFrameInfo frame_info; - unsigned char *mp4_buffer; - unsigned int mp4_buffer_size; - long sample_id; - long num_samples; - long dur; - unsigned int sample_count; - char *sample_buffer; - size_t sample_buffer_length; - unsigned int initial = 1; - float *seek_table; - long seek_table_end = -1; - bool seek_position_found = false; - long offset; - uint16_t bit_rate = 0; - bool seeking = false; - double seek_where = 0; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - 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; - } - - decoder = mp4_faad_new(mp4fh, &track, &audio_format); - if (decoder == NULL) { - mp4ff_close(mp4fh); - return; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - - if (scale < 0) { - g_warning("Error getting audio format of mp4 AAC track.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - total_time = ((float)file_time) / scale; - - num_samples = mp4ff_num_samples(mp4fh, track); - if (num_samples > (long)(G_MAXINT / sizeof(float))) { - g_warning("Integer overflow.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - - file_time = 0.0; - - seek_table = input_stream->seekable - ? g_malloc(sizeof(float) * num_samples) - : NULL; - - decoder_initialized(mpd_decoder, &audio_format, - input_stream->seekable, - total_time); - - for (sample_id = 0; - sample_id < num_samples && cmd != DECODE_COMMAND_STOP; - sample_id++) { - if (cmd == DECODE_COMMAND_SEEK) { - assert(seek_table != NULL); - - seeking = true; - seek_where = decoder_seek_where(mpd_decoder); - } - - if (seeking && seek_table_end > 1 && - seek_table[seek_table_end] >= seek_where) { - int i = 2; - - assert(seek_table != NULL); - - while (seek_table[i] < seek_where) - i++; - sample_id = i - 1; - file_time = seek_table[sample_id]; - } - - dur = mp4ff_get_sample_duration(mp4fh, track, sample_id); - offset = mp4ff_get_sample_offset(mp4fh, track, sample_id); - - if (seek_table != NULL && sample_id > seek_table_end) { - seek_table[sample_id] = file_time; - seek_table_end = sample_id; - } - - if (sample_id == 0) - dur = 0; - if (offset > dur) - dur = 0; - else - dur -= offset; - file_time += ((float)dur) / scale; - - if (seeking && file_time >= seek_where) - seek_position_found = true; - - if (seeking && seek_position_found) { - seek_position_found = false; - seeking = 0; - decoder_command_finished(mpd_decoder); - } - - if (seeking) - continue; - - if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer, - &mp4_buffer_size) == 0) - break; - -#ifdef HAVE_FAAD_BUFLEN_FUNCS - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer, - mp4_buffer_size); -#else - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer); -#endif - - free(mp4_buffer); - - if (frame_info.error > 0) { - g_warning("faad2 error: %s\n", - faacDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - g_warning("channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - if (frame_info.samplerate != audio_format.sample_rate) { - g_warning("sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } -#endif - - if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) { - dur = frame_info.samples / audio_format.channels; - offset = 0; - } - - sample_count = (unsigned long)(dur * audio_format.channels); - - if (sample_count > 0) { - initial = 0; - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * scale / - frame_info.samples / 1000 + 0.5; - } - - sample_buffer_length = sample_count * 2; - - sample_buffer += offset * audio_format.channels * 2; - - cmd = decoder_data(mpd_decoder, input_stream, - sample_buffer, sample_buffer_length, - bit_rate); - } - - g_free(seek_table); - faacDecClose(decoder); - mp4ff_close(mp4fh); -} - -static const struct tag_table mp4ff_tags[] = { - { "album artist", TAG_ALBUM_ARTIST }, - { "writer", TAG_COMPOSER }, - { "band", TAG_PERFORMER }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static enum tag_type -mp4ff_tag_name_parse(const char *name) -{ - enum tag_type type = tag_table_lookup_i(mp4ff_tags, 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 bool -mp4ff_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct mp4ff_input_stream mis; - int32_t track; - int32_t file_time; - int32_t scale; - int i; - - mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is); - if (mp4fh == NULL) - return false; - - track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); - if (track < 0) { - mp4ff_close(mp4fh); - return false; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - if (scale < 0) { - mp4ff_close(mp4fh); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - ((float)file_time) / scale + 0.5); - - for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { - char *item; - char *value; - - mp4ff_meta_get_by_index(mp4fh, i, &item, &value); - - tag_handler_invoke_pair(handler, handler_ctx, item, value); - - enum tag_type type = mp4ff_tag_name_parse(item); - if (type != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, handler_ctx, - type, value); - - free(item); - free(value); - } - - mp4ff_close(mp4fh); - - return true; -} - -static const char *const mp4_suffixes[] = { - "m4a", - "m4b", - "mp4", - NULL -}; - -static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; - -const struct decoder_plugin mp4ff_decoder_plugin = { - .name = "mp4ff", - .stream_decode = mp4_decode, - .scan_stream = mp4ff_scan_stream, - .suffixes = mp4_suffixes, - .mime_types = mp4_mime_types, -}; diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c deleted file mode 100644 index d4768b35b..000000000 --- a/src/decoder/mpcdec_decoder_plugin.c +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_handler.h" - -#ifdef MPC_IS_OLD_API -#include <mpcdec/mpcdec.h> -#else -#include <mpc/mpcdec.h> -#include <math.h> -#endif - -#include <glib.h> -#include <assert.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mpcdec" - -struct mpc_decoder_data { - struct input_stream *is; - struct decoder *decoder; -}; - -#ifdef MPC_IS_OLD_API -#define cb_first_arg void *vdata -#define cb_data vdata -#else -#define cb_first_arg mpc_reader *reader -#define cb_data reader->data -#endif - -static mpc_int32_t -mpc_read_cb(cb_first_arg, void *ptr, mpc_int32_t size) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return decoder_read(data->decoder, data->is, ptr, size); -} - -static mpc_bool_t -mpc_seek_cb(cb_first_arg, mpc_int32_t offset) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return input_stream_lock_seek(data->is, offset, SEEK_SET, NULL); -} - -static mpc_int32_t -mpc_tell_cb(cb_first_arg) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return (long)(data->is->offset); -} - -static mpc_bool_t -mpc_canseek_cb(cb_first_arg) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return data->is->seekable; -} - -static mpc_int32_t -mpc_getsize_cb(cb_first_arg) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return data->is->size; -} - -/* this _looks_ performance-critical, don't de-inline -- eric */ -static inline int32_t -mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) -{ - /* only doing 16-bit audio for now */ - int32_t val; - - enum { - bits = 24, - }; - - const int clip_min = -1 << (bits - 1); - const int clip_max = (1 << (bits - 1)) - 1; - -#ifdef MPC_FIXED_POINT - const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; - - if (shift < 0) - val = sample >> -shift; - else - val = sample << shift; -#else - const int float_scale = 1 << (bits - 1); - - val = sample * float_scale; -#endif - - if (val < clip_min) - val = clip_min; - else if (val > clip_max) - val = clip_max; - - return val; -} - -static void -mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src, - unsigned num_samples) -{ - while (num_samples-- > 0) - *dest++ = mpc_to_mpd_sample(*src++); -} - -static void -mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) -{ -#ifdef MPC_IS_OLD_API - mpc_decoder decoder; -#else - mpc_demux *demux; - mpc_frame_info frame; - mpc_status status; -#endif - mpc_reader reader; - mpc_streaminfo info; - GError *error = NULL; - struct audio_format audio_format; - - struct mpc_decoder_data data; - - MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; - - mpc_uint32_t ret; - int32_t chunk[G_N_ELEMENTS(sample_buffer)]; - long bit_rate = 0; - mpc_uint32_t vbr_update_bits; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - data.is = is; - data.decoder = mpd_decoder; - - reader.read = mpc_read_cb; - reader.seek = mpc_seek_cb; - reader.tell = mpc_tell_cb; - reader.get_size = mpc_getsize_cb; - reader.canseek = mpc_canseek_cb; - reader.data = &data; - -#ifdef MPC_IS_OLD_API - mpc_streaminfo_init(&info); - - if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) - g_warning("Not a valid musepack stream\n"); - return; - } - - mpc_decoder_setup(&decoder, &reader); - - if (!mpc_decoder_initialize(&decoder, &info)) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) - g_warning("Not a valid musepack stream\n"); - return; - } -#else - demux = mpc_demux_init(&reader); - if (demux == NULL) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) - g_warning("Not a valid musepack stream"); - return; - } - - mpc_demux_get_info(demux, &info); -#endif - - 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 - return; - } - - 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; -#else - replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); - replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; - replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); - replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; -#endif - - decoder_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) { - 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, where); -#else - success = mpc_demux_seek_sample(demux, where) - == MPC_STATUS_OK; -#endif - if (success) - decoder_command_finished(mpd_decoder); - else - decoder_seek_error(mpd_decoder); - } - - vbr_update_bits = 0; - -#ifdef MPC_IS_OLD_API - mpc_uint32_t vbr_update_acc = 0; - - ret = mpc_decoder_decode(&decoder, sample_buffer, - &vbr_update_acc, &vbr_update_bits); - if (ret == 0 || ret == (mpc_uint32_t)-1) - break; -#else - frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; - status = mpc_demux_decode(demux, &frame); - if (status != MPC_STATUS_OK) { - g_warning("Failed to decode sample"); - break; - } - - if (frame.bits == -1) - break; - - ret = frame.samples; -#endif - - ret *= info.channels; - - mpc_to_mpd_buffer(chunk, sample_buffer, ret); - - bit_rate = vbr_update_bits * audio_format.sample_rate - / 1152 / 1000; - - cmd = decoder_data(mpd_decoder, is, - chunk, ret * sizeof(chunk[0]), - bit_rate); - } while (cmd != DECODE_COMMAND_STOP); - -#ifndef MPC_IS_OLD_API - mpc_demux_exit(demux); -#endif -} - -static float -mpcdec_get_file_duration(struct input_stream *is) -{ - float total_time = -1; - - mpc_reader reader; -#ifndef MPC_IS_OLD_API - mpc_demux *demux; -#endif - mpc_streaminfo info; - struct mpc_decoder_data data; - - data.is = is; - data.decoder = NULL; - - reader.read = mpc_read_cb; - reader.seek = mpc_seek_cb; - reader.tell = mpc_tell_cb; - reader.get_size = mpc_getsize_cb; - reader.canseek = mpc_canseek_cb; - reader.data = &data; - -#ifdef MPC_IS_OLD_API - mpc_streaminfo_init(&info); - - if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) - return -1; -#else - demux = mpc_demux_init(&reader); - if (demux == NULL) - return -1; - - mpc_demux_get_info(demux, &info); - mpc_demux_exit(demux); -#endif - - total_time = mpc_streaminfo_get_length(&info); - - return total_time; -} - -static bool -mpcdec_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - float total_time = mpcdec_get_file_duration(is); - - if (total_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const mpcdec_suffixes[] = { "mpc", NULL }; - -const struct decoder_plugin mpcdec_decoder_plugin = { - .name = "mpcdec", - .stream_decode = mpcdec_decode, - .scan_stream = mpcdec_scan_stream, - .suffixes = mpcdec_suffixes, -}; diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c deleted file mode 100644 index 657a9c889..000000000 --- a/src/decoder/mpg123_decoder_plugin.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_handler.h" - -#include <glib.h> - -#include <mpg123.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mpg123" - -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; - struct mpg123_frameinfo info; - - /* 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, true, - (float)num_samples / - (float)audio_format.sample_rate); - - if (mpg123_info(handle, &info) != MPG123_OK) { - info.vbr = MPG123_CBR; - info.bitrate = 0; - } - - switch (info.vbr) { - case MPG123_ABR: - info.bitrate = info.abr_rate; - break; - case MPG123_CBR: - break; - default: - info.bitrate = 0; - } - - /* the decoder main loop */ - - do { - 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; - } - - /* update bitrate for ABR/VBR */ - if (info.vbr != MPG123_CBR) { - /* FIXME: maybe skip, as too expensive? */ - /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ - if (mpg123_info (handle, &info) != MPG123_OK) - info.bitrate = 0; - } - - /* send to MPD */ - - cmd = decoder_data(decoder, NULL, buffer, nbytes, info.bitrate); - - if (cmd == DECODE_COMMAND_SEEK) { - off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; - c = mpg123_seek(handle, c, SEEK_SET); - if (c < 0) - decoder_seek_error(decoder); - else { - decoder_command_finished(decoder); - decoder_timestamp(decoder, c/(double)audio_format.sample_rate); - } - - cmd = DECODE_COMMAND_NONE; - } - } while (cmd == DECODE_COMMAND_NONE); - - /* cleanup */ - - mpg123_delete(handle); -} - -static bool -mpd_mpg123_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - struct audio_format audio_format; - mpg123_handle *handle; - int error; - off_t num_samples; - - handle = mpg123_new(NULL, &error); - if (handle == NULL) { - g_warning("mpg123_new() failed: %s", - mpg123_plain_strerror(error)); - return false; - } - - if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { - mpg123_delete(handle); - return false; - } - - num_samples = mpg123_length(handle); - if (num_samples <= 0) { - mpg123_delete(handle); - return false; - } - - /* ID3 tag support not yet implemented */ - - mpg123_delete(handle); - - tag_handler_invoke_duration(handler, handler_ctx, - num_samples / audio_format.sample_rate); - return true; -} - -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 */ - .scan_file = mpd_mpg123_scan_file, - .suffixes = mpg123_suffixes, -}; diff --git a/src/decoder/pcm_decoder_plugin.c b/src/decoder/pcm_decoder_plugin.c deleted file mode 100644 index fc7dffc05..000000000 --- a/src/decoder/pcm_decoder_plugin.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder/pcm_decoder_plugin.h" -#include "decoder_api.h" -#include "util/byte_reverse.h" - -#include <glib.h> -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -static void -pcm_stream_decode(struct decoder *decoder, struct input_stream *is) -{ - static const struct audio_format audio_format = { - .sample_rate = 44100, - .format = SAMPLE_FORMAT_S16, - .channels = 2, - }; - - const bool reverse_endian = is->mime != NULL && - strcmp(is->mime, "audio/x-mpd-cdda-pcm-reverse") == 0; - - GError *error = NULL; - enum decoder_command cmd; - - double time_to_size = audio_format_time_to_size(&audio_format); - - float total_time = -1; - if (is->size >= 0) - total_time = is->size / time_to_size; - - decoder_initialized(decoder, &audio_format, is->seekable, total_time); - - do { - char buffer[4096]; - - size_t nbytes = decoder_read(decoder, is, - buffer, sizeof(buffer)); - - if (nbytes == 0 && input_stream_lock_eof(is)) - break; - - if (reverse_endian) - /* make sure we deliver samples in host byte order */ - reverse_bytes_16((uint16_t *)buffer, - (uint16_t *)buffer, - (uint16_t *)(buffer + nbytes)); - - cmd = nbytes > 0 - ? decoder_data(decoder, is, - buffer, nbytes, 0) - : decoder_get_command(decoder); - if (cmd == DECODE_COMMAND_SEEK) { - goffset offset = (goffset)(time_to_size * - decoder_seek_where(decoder)); - if (input_stream_lock_seek(is, offset, SEEK_SET, - &error)) { - decoder_command_finished(decoder); - } else { - g_warning("seeking failed: %s", error->message); - g_error_free(error); - decoder_seek_error(decoder); - } - - cmd = DECODE_COMMAND_NONE; - } - } while (cmd == DECODE_COMMAND_NONE); -} - -static const char *const pcm_mime_types[] = { - /* for streams obtained by the cdio_paranoia input plugin */ - "audio/x-mpd-cdda-pcm", - - /* same as above, but with reverse byte order */ - "audio/x-mpd-cdda-pcm-reverse", - - NULL -}; - -const struct decoder_plugin pcm_decoder_plugin = { - .name = "pcm", - .stream_decode = pcm_stream_decode, - .mime_types = pcm_mime_types, -}; diff --git a/src/decoder/pcm_decoder_plugin.h b/src/decoder/pcm_decoder_plugin.h deleted file mode 100644 index 11df80155..000000000 --- a/src/decoder/pcm_decoder_plugin.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Not really a decoder; this plugin forwards its input data "as-is". - * - * It was written only to support the "cdio_paranoia" input plugin, - * which does not need a decoder. - */ - -#ifndef MPD_DECODER_PCM_H -#define MPD_DECODER_PCM_H - -extern const struct decoder_plugin pcm_decoder_plugin; - -#endif diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 5d162f179..486dd816f 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -18,25 +18,24 @@ */ #include "config.h" - -extern "C" { -#include "../decoder_api.h" -#include "tag_handler.h" -} +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <errno.h> #include <stdlib.h> +#include <string.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 constexpr Domain sidplay_domain("sidplay"); + static GPatternSpec *path_with_subtune; static const char *songlength_file; static GKeyFile *songlength_database; @@ -54,8 +53,9 @@ sidplay_load_songlength_db(const char *path) gsize size; if (!g_file_get_contents(path, &data, &size, &error)) { - g_warning("unable to read songlengths file %s: %s", - path, error->message); + FormatError(sidplay_domain, + "unable to read songlengths file %s: %s", + path, error->message); g_error_free(error); return NULL; } @@ -70,8 +70,9 @@ sidplay_load_songlength_db(const char *path) G_KEY_FILE_NONE, &error); g_free(data); if (!success) { - g_warning("unable to parse songlengths file %s: %s", - path, error->message); + FormatError(sidplay_domain, + "unable to parse songlengths file %s: %s", + path, error->message); g_error_free(error); g_key_file_free(db); return NULL; @@ -82,29 +83,27 @@ sidplay_load_songlength_db(const char *path) } static bool -sidplay_init(const struct config_param *param) +sidplay_init(const config_param ¶m) { /* read the songlengths database file */ - songlength_file=config_get_block_string(param, - "songlength_database", NULL); + songlength_file = param.GetBlockValue("songlength_database"); if (songlength_file != NULL) songlength_database = sidplay_load_songlength_db(songlength_file); - default_songlength=config_get_block_unsigned(param, - "default_songlength", 0); + default_songlength = param.GetBlockValue("default_songlength", 0u); - all_files_are_containers=config_get_block_bool(param, - "all_files_are_containers", true); + all_files_are_containers = + param.GetBlockValue("all_files_are_containers", true); path_with_subtune=g_pattern_spec_new( "*/" SUBTUNE_PREFIX "???.sid"); - filter_setting=config_get_block_bool(param, "filter", true); + filter_setting = param.GetBlockValue("filter", true); return true; } -void +static void sidplay_finish() { g_pattern_spec_free(path_with_subtune); @@ -136,7 +135,7 @@ get_container_name(const char *path_fs) * returns tune number from file.sid/tune_xxx.sid style path or 1 if * no subtune is appended */ -static int +static unsigned get_song_num(const char *path_fs) { if(g_pattern_match(path_with_subtune, @@ -166,13 +165,14 @@ get_song_length(const char *path_fs) SidTuneMod tune(sid_file); g_free(sid_file); if(!tune) { - g_warning("failed to load file for calculating md5 sum"); + LogWarning(sidplay_domain, + "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); + const unsigned song_num = get_song_num(path_fs); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, @@ -209,7 +209,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) SidTune tune(path_container, NULL, true); g_free(path_container); if (!tune) { - g_warning("failed to load file"); + LogWarning(sidplay_domain, "failed to load file"); return; } @@ -224,7 +224,8 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) sidplay2 player; int iret = player.load(&tune); if (iret != 0) { - g_warning("sidplay2.load() failed: %s", player.error()); + FormatWarning(sidplay_domain, + "sidplay2.load() failed: %s", player.error()); return; } @@ -232,19 +233,20 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) ReSIDBuilder builder("ReSID"); if (!builder) { - g_warning("failed to initialize ReSIDBuilder"); + LogWarning(sidplay_domain, + "failed to initialize ReSIDBuilder"); return; } builder.create(player.info().maxsids); if (!builder) { - g_warning("ReSIDBuilder.create() failed"); + LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); return; } builder.filter(filter_setting); if (!builder) { - g_warning("ReSIDBuilder.filter() failed"); + LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed"); return; } @@ -278,24 +280,24 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) iret = player.config(config); if (iret != 0) { - g_warning("sidplay2.config() failed: %s", player.error()); + FormatWarning(sidplay_domain, + "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)); + const AudioFormat audio_format(48000, SampleFormat::S16, channels); + assert(audio_format.IsValid()); - decoder_initialized(decoder, &audio_format, true, (float)song_len); + decoder_initialized(decoder, audio_format, true, (float)song_len); /* .. and play */ const unsigned timebase = player.timebase(); song_len *= timebase; - enum decoder_command cmd; + DecoderCommand cmd; do { char buffer[4096]; size_t nbytes; @@ -308,7 +310,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); - if(cmd==DECODE_COMMAND_SEEK) { + if (cmd == DecoderCommand::SEEK) { unsigned data_time = player.time(); unsigned target_time = (unsigned) (decoder_seek_where(decoder) * timebase); @@ -330,10 +332,10 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) decoder_command_finished(decoder); } - if (song_len > 0 && player.time() >= song_len) + if (song_len > 0 && player.time() >= (unsigned)song_len) break; - } while (cmd != DECODE_COMMAND_STOP); + } while (cmd != DecoderCommand::STOP); } static bool diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c deleted file mode 100644 index 8dd98236f..000000000 --- a/src/decoder/sndfile_decoder_plugin.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "tag_handler.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_lock_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_lock_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 bool -sndfile_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - SNDFILE *sf; - SF_INFO info; - const char *p; - - info.format = 0; - - sf = sf_open(path_fs, SFM_READ, &info); - if (sf == NULL) - return false; - - if (!audio_valid_sample_rate(info.samplerate)) { - sf_close(sf); - g_warning("Invalid sample rate in %s\n", path_fs); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - info.frames / info.samplerate); - - p = sf_get_string(sf, SF_STR_TITLE); - if (p != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, p); - - p = sf_get_string(sf, SF_STR_ARTIST); - if (p != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, p); - - p = sf_get_string(sf, SF_STR_DATE); - if (p != NULL) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, p); - - sf_close(sf); - - return true; -} - -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, - .scan_file = sndfile_scan_file, - .suffixes = sndfile_suffixes, - .mime_types = sndfile_mime_types, -}; diff --git a/src/decoder/vorbis_comments.c b/src/decoder/vorbis_comments.c deleted file mode 100644 index 6c2d57b72..000000000 --- a/src/decoder/vorbis_comments.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "vorbis_comments.h" -#include "tag.h" -#include "tag_table.h" -#include "tag_handler.h" -#include "replay_gain_info.h" - -#include <glib.h> -#include <assert.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> - -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (g_ascii_strncasecmp(comment, needle, len) == 0 && - comment[len] == '=') - return comment + len + 1; - - return NULL; -} - -bool -vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) -{ - const char *temp; - bool found = false; - - replay_gain_info_init(rgi); - - while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } - - comments++; - } - - return found; -} - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -vorbis_copy_comment(const char *comment, - const char *name, enum tag_type tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != NULL) { - tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); - return true; - } - - return false; -} - -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -vorbis_scan_comment(const char *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != NULL) { - char *name = g_strdup((const char*)comment); - char *value = strchr(name, '='); - - if (value != NULL && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) - if (vorbis_copy_comment(comment, i->name, i->type, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(comment, - tag_item_names[i], i, - handler, handler_ctx)) - return; -} - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx) -{ - while (*comments) - vorbis_scan_comment(*comments++, - handler, handler_ctx); - -} - -struct tag * -vorbis_comments_to_tag(char **comments) -{ - struct tag *tag = tag_new(); - vorbis_comments_scan(comments, &add_tag_handler, tag); - - if (tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} diff --git a/src/decoder/vorbis_comments.h b/src/decoder/vorbis_comments.h deleted file mode 100644 index c15096930..000000000 --- a/src/decoder/vorbis_comments.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VORBIS_COMMENTS_H -#define MPD_VORBIS_COMMENTS_H - -#include "check.h" - -#include <stdbool.h> - -struct replay_gain_info; -struct tag_handler; - -bool -vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments); - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx); - -struct tag * -vorbis_comments_to_tag(char **comments); - -#endif diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c deleted file mode 100644 index 15cdc0ca9..000000000 --- a/src/decoder/vorbis_decoder_plugin.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "vorbis_comments.h" -#include "_ogg_common.h" -#include "audio_check.h" -#include "uri.h" -#include "tag_handler.h" - -#ifndef HAVE_TREMOR -#define OV_EXCLUDE_STATIC_CALLBACKS -#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. -*/ -#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ - ov_read(VF, BUFFER, LENGTH, BITSTREAM) -#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) -#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) -#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) -#endif /* HAVE_TREMOR */ - -#include <glib.h> - -#include <assert.h> -#include <errno.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 - -#if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 -#else -#define OGG_DECODE_USE_BIGENDIAN 0 -#endif - -struct vorbis_input_stream { - struct decoder *decoder; - - struct input_stream *input_stream; - bool seekable; -}; - -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; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); - - errno = 0; - - return ret / size; -} - -static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) -{ - struct vorbis_input_stream *vis = data; - - return vis->seekable && - (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && - input_stream_lock_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 *data) -{ - return 0; -} - -static long ogg_tell_cb(void *data) -{ - const struct vorbis_input_stream *vis = data; - - return (long)vis->input_stream->offset; -} - -static const ov_callbacks vorbis_is_callbacks = { - .read_func = ogg_read_cb, - .seek_func = ogg_seek_cb, - .close_func = ogg_close_cb, - .tell_func = ogg_tell_cb, -}; - -static const char * -vorbis_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 void -vorbis_send_comments(struct decoder *decoder, struct input_stream *is, - char **comments) -{ - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); - if (!tag) - return; - - decoder_tag(decoder, is, tag); - tag_free(tag); -} - -/* public */ -static void -vorbis_stream_decode(struct decoder *decoder, - struct input_stream *input_stream) -{ - GError *error = NULL; - OggVorbis_File vf; - 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; - const vorbis_info *vi; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); - - if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) - return; - - vi = ov_info(&vf, -1); - if (vi == NULL) { - g_warning("ov_info() has failed"); - return; - } - - 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; - } - - 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) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) - /* break on EOF or other error */ - break; - - if (current_section != prev_section) { - char **comments; - - vi = ov_info(&vf, -1); - if (vi == NULL) { - g_warning("ov_info() has failed"); - break; - } - - 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); - - 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, - bitRate); - } while (cmd != DECODE_COMMAND_STOP); - - ov_clear(&vf); -} - -static bool -vorbis_scan_stream(struct input_stream *is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct vorbis_input_stream vis; - OggVorbis_File vf; - - if (!vorbis_is_open(&vis, &vf, NULL, is)) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - (int)(ov_time_total(&vf, -1) + 0.5)); - - vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, - handler, handler_ctx); - - ov_clear(&vf); - return true; -} - -static const char *const vorbis_suffixes[] = { - "ogg", "oga", NULL -}; - -static const char *const vorbis_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - NULL -}; - -const struct decoder_plugin vorbis_decoder_plugin = { - .name = "vorbis", - .stream_decode = vorbis_stream_decode, - .scan_stream = vorbis_scan_stream, - .suffixes = vorbis_suffixes, - .mime_types = vorbis_mime_types -}; diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c deleted file mode 100644 index 9ebd0fccc..000000000 --- a/src/decoder/wavpack_decoder_plugin.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "path.h" -#include "utils.h" -#include "tag_table.h" -#include "tag_handler.h" -#include "tag_ape.h" - -#include <wavpack/wavpack.h> -#include <glib.h> - -#include <assert.h> -#include <unistd.h> -#include <stdio.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "wavpack" - -#define ERRORLEN 80 - -/** A pointer type for format converter function. */ -typedef void (*format_samples_t)( - int bytes_per_sample, - void *buffer, uint32_t count -); - -/* - * This function has been borrowed from the tiny player found on - * wavpack.com. Modifications were required because mpd only handles - * max 24-bit samples. - */ -static void -format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) -{ - int32_t *src = buffer; - - switch (bytes_per_sample) { - case 1: { - int8_t *dst = buffer; - /* - * The asserts like the following one are because we do the - * formatting of samples within a single buffer. The size - * of the output samples never can be greater than the size - * of the input ones. Otherwise we would have an overflow. - */ - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* pass through and align 8-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - case 2: { - uint16_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* pass through and align 16-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - - case 3: - case 4: - /* do nothing */ - break; - } -} - -/* - * This function converts floating point sample data to 24-bit integer. - */ -static void -format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, - uint32_t count) -{ - float *p = buffer; - - while (count--) { - *p /= (1 << 23); - ++p; - } -} - -/** - * 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_FLOAT; - - 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) -{ - GError *error = NULL; - bool is_float; - enum sample_format sample_format; - struct audio_format audio_format; - format_samples_t format_samples; - float total_time; - int bytes_per_sample, output_sample_size; - - 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; - } - - if (is_float) { - format_samples = format_samples_float; - } else { - format_samples = format_samples_int; - } - - total_time = WavpackGetNumSamples(wpc); - total_time /= audio_format.sample_rate; - bytes_per_sample = WavpackGetBytesPerSample(wpc); - output_sample_size = audio_format_frame_size(&audio_format); - - /* wavpack gives us all kind of samples in a 32-bit space */ - int32_t chunk[1024]; - const uint32_t samples_requested = G_N_ELEMENTS(chunk) / - audio_format.channels; - - decoder_initialized(decoder, &audio_format, can_seek, total_time); - - enum decoder_command cmd = decoder_get_command(decoder); - while (cmd != DECODE_COMMAND_STOP) { - if (cmd == DECODE_COMMAND_SEEK) { - if (can_seek) { - unsigned where = decoder_seek_where(decoder) * - audio_format.sample_rate; - - if (WavpackSeekSample(wpc, where)) { - decoder_command_finished(decoder); - } else { - decoder_seek_error(decoder); - } - } else { - decoder_seek_error(decoder); - } - } - - uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, - samples_requested); - if (samples_got == 0) - break; - - int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + - 0.5); - format_samples(bytes_per_sample, chunk, - samples_got * audio_format.channels); - - cmd = decoder_data(decoder, NULL, chunk, - samples_got * output_sample_size, - bitrate); - } -} - -/** - * Locate and parse a floating point tag. Returns true if it was - * found. - */ -static bool -wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) -{ - char buffer[64]; - int ret; - - ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)); - if (ret <= 0) - return false; - - *value_r = atof(buffer); - return true; -} - -static bool -wavpack_replaygain(struct replay_gain_info *replay_gain_info, - WavpackContext *wpc) -{ - bool found = false; - - replay_gain_info_init(replay_gain_info); - - found |= wavpack_tag_float( - wpc, "replaygain_track_gain", - &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain - ); - found |= wavpack_tag_float( - wpc, "replaygain_track_peak", - &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak - ); - found |= wavpack_tag_float( - wpc, "replaygain_album_gain", - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain - ); - found |= wavpack_tag_float( - wpc, "replaygain_album_peak", - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak - ); - - return found; -} - -static void -wavpack_scan_tag_item(WavpackContext *wpc, const char *name, - enum tag_type type, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[1024]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_tag(handler, handler_ctx, type, buffer); - -} - -static void -wavpack_scan_pair(WavpackContext *wpc, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[8192]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_pair(handler, handler_ctx, name, buffer); -} - -/* - * Reads metainfo from the specified file. - */ -static bool -wavpack_scan_file(const char *fname, - const struct tag_handler *handler, void *handler_ctx) -{ - WavpackContext *wpc; - char error[ERRORLEN]; - - wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == NULL) { - g_warning( - "failed to open WavPack file \"%s\": %s\n", - fname, error - ); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - WavpackGetNumSamples(wpc) / - WavpackGetSampleRate(wpc)); - - /* the WavPack format implies APEv2 tags, which means we can - reuse the mapping from tag_ape.c */ - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - const char *name = tag_item_names[i]; - if (name != NULL) - wavpack_scan_tag_item(wpc, name, (enum tag_type)i, - handler, handler_ctx); - } - - for (const struct tag_table *i = ape_tags; i->name != NULL; ++i) - wavpack_scan_tag_item(wpc, i->name, i->type, - handler, handler_ctx); - - if (handler->pair != NULL) { - char name[64]; - - for (int i = 0, n = WavpackGetNumTagItems(wpc); - i < n; ++i) { - int len = WavpackGetTagItemIndexed(wpc, i, name, - sizeof(name)); - if (len <= 0 || (unsigned)len >= sizeof(name)) - continue; - - wavpack_scan_pair(wpc, name, handler, handler_ctx); - } - } - - WavpackCloseFile(wpc); - - return true; -} - -/* - * mpd input_stream <=> WavpackStreamReader wrapper callbacks - */ - -/* This struct is needed for per-stream last_byte storage. */ -struct wavpack_input { - struct decoder *decoder; - struct input_stream *is; - /* Needed for push_back_byte() */ - int last_byte; -}; - -/** - * Little wrapper for struct wavpack_input to cast from void *. - */ -static struct wavpack_input * -wpin(void *id) -{ - assert(id); - return id; -} - -static int32_t -wavpack_input_read_bytes(void *id, void *data, int32_t bcount) -{ - uint8_t *buf = (uint8_t *)data; - int32_t i = 0; - - if (wpin(id)->last_byte != EOF) { - *buf++ = wpin(id)->last_byte; - wpin(id)->last_byte = EOF; - --bcount; - ++i; - } - - /* wavpack fails if we return a partial read, so we just wait - until the buffer is full */ - while (bcount > 0) { - size_t nbytes = decoder_read( - wpin(id)->decoder, wpin(id)->is, buf, bcount - ); - if (nbytes == 0) { - /* EOF, error or a decoder command */ - break; - } - - i += nbytes; - bcount -= nbytes; - buf += nbytes; - } - - return i; -} - -static uint32_t -wavpack_input_get_pos(void *id) -{ - return wpin(id)->is->offset; -} - -static int -wavpack_input_set_pos_abs(void *id, uint32_t pos) -{ - return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL) - ? 0 : -1; -} - -static int -wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) -{ - return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL) - ? 0 : -1; -} - -static int -wavpack_input_push_back_byte(void *id, int c) -{ - if (wpin(id)->last_byte == EOF) { - wpin(id)->last_byte = c; - return c; - } else { - return EOF; - } -} - -static uint32_t -wavpack_input_get_length(void *id) -{ - if (wpin(id)->is->size < 0) - return 0; - - return wpin(id)->is->size; -} - -static int -wavpack_input_can_seek(void *id) -{ - return wpin(id)->is->seekable; -} - -static WavpackStreamReader mpd_is_reader = { - .read_bytes = wavpack_input_read_bytes, - .get_pos = wavpack_input_get_pos, - .set_pos_abs = wavpack_input_set_pos_abs, - .set_pos_rel = wavpack_input_set_pos_rel, - .push_back_byte = wavpack_input_push_back_byte, - .get_length = wavpack_input_get_length, - .can_seek = wavpack_input_can_seek, - .write_bytes = NULL /* no need to write edited tags */ -}; - -static void -wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, - struct input_stream *is) -{ - isp->decoder = decoder; - isp->is = is; - isp->last_byte = EOF; -} - -static struct input_stream * -wavpack_open_wvc(struct decoder *decoder, const char *uri, - GMutex *mutex, GCond *cond, - struct wavpack_input *wpi) -{ - struct input_stream *is_wvc; - char *wvc_url = NULL; - char first_byte; - size_t nbytes; - - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - if (uri == NULL) - return false; - - wvc_url = g_strconcat(uri, "c", NULL); - is_wvc = input_stream_open(wvc_url, mutex, cond, NULL); - g_free(wvc_url); - - if (is_wvc == NULL) - return NULL; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - nbytes = decoder_read( - decoder, is_wvc, &first_byte, sizeof(first_byte) - ); - if (nbytes == 0) { - input_stream_close(is_wvc); - return NULL; - } - - /* push it back */ - wavpack_input_init(wpi, decoder, is_wvc); - wpi->last_byte = first_byte; - return is_wvc; -} - -/* - * Decodes a stream. - */ -static void -wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - struct input_stream *is_wvc; - int open_flags = OPEN_NORMALIZE; - struct wavpack_input isp, isp_wvc; - bool can_seek = is->seekable; - - is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond, - &isp_wvc); - if (is_wvc != NULL) { - open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; - } - - if (!can_seek) { - open_flags |= OPEN_STREAMING; - } - - wavpack_input_init(&isp, decoder, is); - wpc = WavpackOpenFileInputEx( - &mpd_is_reader, &isp, - open_flags & OPEN_WVC ? &isp_wvc : NULL, - error, open_flags, 23 - ); - - if (wpc == NULL) { - g_warning("failed to open WavPack stream: %s\n", error); - return; - } - - wavpack_decode(decoder, wpc, can_seek); - - WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) { - input_stream_close(is_wvc); - } -} - -/* - * Decodes a file. - */ -static void -wavpack_filedecode(struct decoder *decoder, const char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - - wpc = WavpackOpenFileInput( - fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 - ); - if (wpc == NULL) { - g_warning( - "failed to open WavPack file \"%s\": %s\n", - fname, error - ); - return; - } - - 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); - - WavpackCloseFile(wpc); -} - -static char const *const wavpack_suffixes[] = { - "wv", - NULL -}; - -static char const *const wavpack_mime_types[] = { - "audio/x-wavpack", - NULL -}; - -const struct decoder_plugin wavpack_decoder_plugin = { - .name = "wavpack", - .stream_decode = wavpack_streamdecode, - .file_decode = wavpack_filedecode, - .scan_file = wavpack_scan_file, - .suffixes = wavpack_suffixes, - .mime_types = wavpack_mime_types -}; diff --git a/src/decoder/wildmidi_decoder_plugin.c b/src/decoder/wildmidi_decoder_plugin.c deleted file mode 100644 index 2cdb30a9c..000000000 --- a/src/decoder/wildmidi_decoder_plugin.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "tag_handler.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <wildmidi_lib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "wildmidi" - -enum { - WILDMIDI_SAMPLE_RATE = 48000, -}; - -static bool -wildmidi_init(const struct config_param *param) -{ - const char *config_file; - int ret; - - config_file = config_get_block_string(param, "config_file", - "/etc/timidity/timidity.cfg"); - if (!g_file_test(config_file, G_FILE_TEST_IS_REGULAR)) { - g_debug("configuration file does not exist: %s", config_file); - return false; - } - - ret = WildMidi_Init(config_file, WILDMIDI_SAMPLE_RATE, 0); - return ret == 0; -} - -static void -wildmidi_finish(void) -{ - WildMidi_Shutdown(); -} - -static void -wildmidi_file_decode(struct decoder *decoder, const char *path_fs) -{ - static const struct audio_format audio_format = { - .sample_rate = WILDMIDI_SAMPLE_RATE, - .format = SAMPLE_FORMAT_S16, - .channels = 2, - }; - midi *wm; - const struct _WM_Info *info; - enum decoder_command cmd; - - wm = WildMidi_Open(path_fs); - if (wm == NULL) - return; - - info = WildMidi_GetInfo(wm); - if (info == NULL) { - WildMidi_Close(wm); - return; - } - - decoder_initialized(decoder, &audio_format, true, - info->approx_total_samples / WILDMIDI_SAMPLE_RATE); - - do { - char buffer[4096]; - int len; - - info = WildMidi_GetInfo(wm); - if (info == NULL) - break; - - len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); - if (len <= 0) - break; - - cmd = decoder_data(decoder, NULL, buffer, len, 0); - - if (cmd == DECODE_COMMAND_SEEK) { - unsigned long seek_where = WILDMIDI_SAMPLE_RATE * - decoder_seek_where(decoder); - -#ifdef HAVE_WILDMIDI_SAMPLED_SEEK - WildMidi_SampledSeek(wm, &seek_where); -#else - WildMidi_FastSeek(wm, &seek_where); -#endif - decoder_command_finished(decoder); - cmd = DECODE_COMMAND_NONE; - } - - } while (cmd == DECODE_COMMAND_NONE); - - WildMidi_Close(wm); -} - -static bool -wildmidi_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - midi *wm = WildMidi_Open(path_fs); - if (wm == NULL) - return false; - - const struct _WM_Info *info = WildMidi_GetInfo(wm); - if (info == NULL) { - WildMidi_Close(wm); - return false; - } - - int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; - tag_handler_invoke_duration(handler, handler_ctx, duration); - - WildMidi_Close(wm); - - return true; -} - -static const char *const wildmidi_suffixes[] = { - "mid", - NULL -}; - -const struct decoder_plugin wildmidi_decoder_plugin = { - .name = "wildmidi", - .init = wildmidi_init, - .finish = wildmidi_finish, - .file_decode = wildmidi_file_decode, - .scan_file = wildmidi_scan_file, - .suffixes = wildmidi_suffixes, -}; diff --git a/src/decoder_api.c b/src/decoder_api.c deleted file mode 100644 index a45d0f1e6..000000000 --- a/src/decoder_api.c +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "audio_config.h" -#include "song.h" -#include "buffer.h" -#include "pipe.h" -#include "chunk.h" -#include "replay_gain_config.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder" - -void -decoder_initialized(struct decoder *decoder, - const struct audio_format *audio_format, - bool seekable, float total_time) -{ - 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); - assert(!decoder->seeking); - assert(audio_format != NULL); - 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->seekable = seekable; - dc->total_time = total_time; - - decoder_lock(dc); - dc->state = DECODE_STATE_DECODE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); - - 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 %s", - audio_format_to_string(&dc->out_audio_format, - &af_string)); -} - -/** - * Checks if we need an "initial seek". If so, then the initial seek - * is prepared, and the function returns true. - */ -G_GNUC_PURE -static bool -decoder_prepare_initial_seek(struct decoder *decoder) -{ - const struct decoder_control *dc = decoder->dc; - assert(dc->pipe != NULL); - - if (dc->state != DECODE_STATE_DECODE) - /* wait until the decoder has finished initialisation - (reading file headers etc.) before emitting the - virtual "SEEK" command */ - return false; - - if (decoder->initial_seek_running) - /* initial seek has already begun - override any other - command */ - return true; - - if (decoder->initial_seek_pending) { - if (!dc->seekable) { - /* seeking is not possible */ - decoder->initial_seek_pending = false; - return false; - } - - if (dc->command == DECODE_COMMAND_NONE) { - /* begin initial seek */ - - decoder->initial_seek_pending = false; - decoder->initial_seek_running = true; - return true; - } - - /* skip initial seek when there's another command - (e.g. STOP) */ - - decoder->initial_seek_pending = false; - } - - return false; -} - -/** - * Returns the current decoder command. May return a "virtual" - * synthesized command, e.g. to seek to the beginning of the CUE - * track. - */ -G_GNUC_PURE -static enum decoder_command -decoder_get_virtual_command(struct decoder *decoder) -{ - const struct decoder_control *dc = decoder->dc; - assert(dc->pipe != NULL); - - if (decoder_prepare_initial_seek(decoder)) - return DECODE_COMMAND_SEEK; - - return dc->command; -} - -enum decoder_command -decoder_get_command(struct decoder *decoder) -{ - return decoder_get_virtual_command(decoder); -} - -void -decoder_command_finished(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - - decoder_lock(dc); - - assert(dc->command != DECODE_COMMAND_NONE || - decoder->initial_seek_running); - assert(dc->command != DECODE_COMMAND_SEEK || - decoder->initial_seek_running || - dc->seek_error || decoder->seeking); - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) { - assert(!decoder->seeking); - assert(decoder->chunk == NULL); - assert(music_pipe_empty(dc->pipe)); - - decoder->initial_seek_running = false; - decoder->timestamp = dc->start_ms / 1000.; - decoder_unlock(dc); - return; - } - - if (decoder->seeking) { - decoder->seeking = false; - - /* delete frames from the old song position */ - - if (decoder->chunk != NULL) { - music_buffer_return(dc->buffer, decoder->chunk); - decoder->chunk = NULL; - } - - music_pipe_clear(dc->pipe, dc->buffer); - - decoder->timestamp = dc->seek_where; - } - - dc->command = DECODE_COMMAND_NONE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); -} - -double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) -{ - const struct decoder_control *dc = decoder->dc; - - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) - return dc->start_ms / 1000.; - - assert(dc->command == DECODE_COMMAND_SEEK); - - decoder->seeking = true; - - return dc->seek_where; -} - -void decoder_seek_error(struct decoder * decoder) -{ - struct decoder_control *dc = decoder->dc; - - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) { - /* d'oh, we can't seek to the sub-song start position, - what now? - no idea, ignoring the problem for now. */ - decoder->initial_seek_running = false; - return; - } - - assert(dc->command == DECODE_COMMAND_SEEK); - - dc->seek_error = true; - decoder->seeking = false; - - decoder_command_finished(decoder); -} - -/** - * Should be read operation be cancelled? That is the case when the - * player thread has sent a command such as "STOP". - */ -G_GNUC_PURE -static inline bool -decoder_check_cancel_read(const struct decoder *decoder) -{ - if (decoder == NULL) - return false; - - const struct decoder_control *dc = decoder->dc; - if (dc->command == DECODE_COMMAND_NONE) - return false; - - /* ignore the SEEK command during initialization, the plugin - should handle that after it has initialized successfully */ - if (dc->command == DECODE_COMMAND_SEEK && - (dc->state == DECODE_STATE_START || decoder->seeking)) - return false; - - return true; -} - -size_t decoder_read(struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - /* XXX don't allow decoder==NULL */ - GError *error = NULL; - size_t nbytes; - - assert(decoder == NULL || - decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - assert(is != NULL); - assert(buffer != NULL); - - if (length == 0) - return 0; - - input_stream_lock(is); - - while (true) { - if (decoder_check_cancel_read(decoder)) { - input_stream_unlock(is); - return 0; - } - - if (input_stream_available(is)) - break; - - g_cond_wait(is->cond, is->mutex); - } - - nbytes = input_stream_read(is, buffer, length, &error); - assert(nbytes == 0 || error == NULL); - assert(nbytes > 0 || error != NULL || input_stream_eof(is)); - - if (G_UNLIKELY(nbytes == 0 && error != NULL)) { - g_warning("%s", error->message); - g_error_free(error); - } - - input_stream_unlock(is); - - return nbytes; -} - -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. - */ -static enum decoder_command -do_send_tag(struct decoder *decoder, const struct tag *tag) -{ - struct music_chunk *chunk; - - if (decoder->chunk != NULL) { - /* there is a partial chunk - flush it, we want the - tag in a new chunk */ - decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); - } - - assert(decoder->chunk == NULL); - - chunk = decoder_get_chunk(decoder); - if (chunk == NULL) { - assert(decoder->dc->command != DECODE_COMMAND_NONE); - return decoder->dc->command; - } - - chunk->tag = tag_dup(tag); - return DECODE_COMMAND_NONE; -} - -static bool -update_stream_tag(struct decoder *decoder, struct input_stream *is) -{ - struct tag *tag; - - tag = is != NULL - ? input_stream_lock_tag(is) - : NULL; - if (tag == NULL) { - tag = decoder->song_tag; - if (tag == NULL) - return false; - - /* no stream tag present - submit the song tag - instead */ - decoder->song_tag = NULL; - } - - if (decoder->stream_tag != NULL) - tag_free(decoder->stream_tag); - - decoder->stream_tag = tag; - return true; -} - -enum decoder_command -decoder_data(struct decoder *decoder, - struct input_stream *is, - const void *_data, size_t length, - 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); - - decoder_lock(dc); - cmd = decoder_get_virtual_command(decoder); - decoder_unlock(dc); - - if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || - length == 0) - return cmd; - - /* send stream tags */ - - if (update_stream_tag(decoder, is)) { - if (decoder->decoder_tag != NULL) { - /* merge with tag from decoder plugin */ - struct tag *tag; - - tag = tag_merge(decoder->decoder_tag, - decoder->stream_tag); - cmd = do_send_tag(decoder, tag); - tag_free(tag); - } else - /* send only the stream tag */ - cmd = do_send_tag(decoder, decoder->stream_tag); - - if (cmd != DECODE_COMMAND_NONE) - return cmd; - } - - 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, - &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) { - struct music_chunk *chunk; - char *dest; - size_t nbytes; - bool full; - - chunk = decoder_get_chunk(decoder); - if (chunk == NULL) { - assert(dc->command != DECODE_COMMAND_NONE); - return dc->command; - } - - 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); - g_cond_signal(dc->client_cond); - continue; - } - - assert(nbytes > 0); - - if (nbytes > length) - nbytes = length; - - /* copy the buffer */ - - memcpy(dest, data, nbytes); - - /* expand the music pipe chunk */ - - full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); - if (full) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); - } - - data += nbytes; - length -= nbytes; - - decoder->timestamp += (double)nbytes / - audio_format_time_to_size(&dc->out_audio_format); - - if (dc->end_ms > 0 && - decoder->timestamp >= dc->end_ms / 1000.0) - /* the end of this range has been reached: - stop decoding */ - return DECODE_COMMAND_STOP; - } - - return DECODE_COMMAND_NONE; -} - -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(tag != NULL); - - /* save the tag */ - - if (decoder->decoder_tag != NULL) - tag_free(decoder->decoder_tag); - decoder->decoder_tag = tag_dup(tag); - - /* check for a new stream tag */ - - update_stream_tag(decoder, is); - - /* check if we're seeking */ - - if (decoder_prepare_initial_seek(decoder)) - /* during initial seek, no music chunk must be created - until seeking is finished; skip the rest of the - function here */ - return DECODE_COMMAND_SEEK; - - /* send tag to music pipe */ - - if (decoder->stream_tag != NULL) { - /* merge with tag from input stream */ - struct tag *merged; - - merged = tag_merge(decoder->stream_tag, decoder->decoder_tag); - cmd = do_send_tag(decoder, merged); - tag_free(merged); - } else - /* send only the decoder tag */ - cmd = do_send_tag(decoder, tag); - - 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); - g_cond_signal(decoder->dc->client_cond); - } - } 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 deleted file mode 100644 index 6e011c395..000000000 --- a/src/decoder_api.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/*! \file - * \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_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); - -/** - * Determines the pending decoder command. - * - * @param decoder the decoder object - * @return the current command, or DECODE_COMMAND_NONE if there is no - * command pending - */ -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 - */ -double -decoder_seek_where(struct decoder *decoder); - -/** - * Call this instead of decoder_command_finished() when seeking has - * failed. - * - * @param decoder the decoder object - */ -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); - -/** - * 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. - */ -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. - * - * @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 *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 deleted file mode 100644 index fcb135976..000000000 --- a/src/decoder_buffer.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_buffer.h" -#include "decoder_api.h" - -#include <glib.h> - -#include <assert.h> - -struct decoder_buffer { - struct decoder *decoder; - struct input_stream *is; - - /** the allocated size of the buffer */ - size_t size; - - /** the current length of the buffer */ - size_t length; - - /** number of bytes already consumed at the beginning of the - buffer */ - size_t consumed; - - /** the actual buffer (dynamic size) */ - unsigned char data[sizeof(size_t)]; -}; - -struct decoder_buffer * -decoder_buffer_new(struct decoder *decoder, struct input_stream *is, - size_t size) -{ - struct decoder_buffer *buffer = - g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - - assert(is != NULL); - assert(size > 0); - - buffer->decoder = decoder; - buffer->is = is; - buffer->size = size; - buffer->length = 0; - buffer->consumed = 0; - - return buffer; -} - -void -decoder_buffer_free(struct decoder_buffer *buffer) -{ - assert(buffer != NULL); - - g_free(buffer); -} - -bool -decoder_buffer_is_empty(const struct decoder_buffer *buffer) -{ - return buffer->consumed == buffer->length; -} - -bool -decoder_buffer_is_full(const struct decoder_buffer *buffer) -{ - return buffer->consumed == 0 && buffer->length == buffer->size; -} - -static void -decoder_buffer_shift(struct decoder_buffer *buffer) -{ - assert(buffer->consumed > 0); - - buffer->length -= buffer->consumed; - memmove(buffer->data, buffer->data + buffer->consumed, buffer->length); - buffer->consumed = 0; -} - -bool -decoder_buffer_fill(struct decoder_buffer *buffer) -{ - size_t nbytes; - - if (buffer->consumed > 0) - decoder_buffer_shift(buffer); - - if (buffer->length >= buffer->size) - /* buffer is full */ - return false; - - nbytes = decoder_read(buffer->decoder, buffer->is, - buffer->data + buffer->length, - buffer->size - buffer->length); - if (nbytes == 0) - /* end of file, I/O error or decoder command - received */ - return false; - - buffer->length += nbytes; - assert(buffer->length <= buffer->size); - - return true; -} - -const void * -decoder_buffer_read(const struct decoder_buffer *buffer, size_t *length_r) -{ - if (buffer->consumed >= buffer->length) - /* buffer is empty */ - return NULL; - - *length_r = buffer->length - buffer->consumed; - return buffer->data + buffer->consumed; -} - -void -decoder_buffer_consume(struct decoder_buffer *buffer, size_t nbytes) -{ - /* just move the "consumed" pointer - decoder_buffer_shift() - will do the real work later (called by - decoder_buffer_fill()) */ - buffer->consumed += nbytes; - - assert(buffer->consumed <= buffer->length); -} - -bool -decoder_buffer_skip(struct decoder_buffer *buffer, size_t nbytes) -{ - size_t length; - const void *data; - bool success; - - /* this could probably be optimized by seeking */ - - while (true) { - data = decoder_buffer_read(buffer, &length); - if (data != NULL) { - if (length > nbytes) - length = nbytes; - decoder_buffer_consume(buffer, length); - nbytes -= length; - if (nbytes == 0) - return true; - } - - success = decoder_buffer_fill(buffer); - if (!success) - return false; - } -} diff --git a/src/decoder_buffer.h b/src/decoder_buffer.h deleted file mode 100644 index 77eff5dd1..000000000 --- a/src/decoder_buffer.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_BUFFER_H -#define MPD_DECODER_BUFFER_H - -#include <stdbool.h> -#include <stddef.h> - -/** - * This objects handles buffered reads in decoder plugins easily. You - * create a buffer object, and use its high-level methods to fill and - * read it. It will automatically handle shifting the buffer. - */ -struct decoder_buffer; - -struct decoder; -struct input_stream; - -/** - * Creates a new buffer. - * - * @param decoder the decoder object, used for decoder_read(), may be NULL - * @param is the input stream object where we should read from - * @param size the maximum size of the buffer - * @return the new decoder_buffer object - */ -struct decoder_buffer * -decoder_buffer_new(struct decoder *decoder, struct input_stream *is, - size_t size); - -/** - * Frees resources used by the decoder_buffer object. - */ -void -decoder_buffer_free(struct decoder_buffer *buffer); - -bool -decoder_buffer_is_empty(const struct decoder_buffer *buffer); - -bool -decoder_buffer_is_full(const struct decoder_buffer *buffer); - -/** - * Read data from the input_stream and append it to the buffer. - * - * @return true if data was appended; false if there is no data - * available (yet), end of file, I/O error or a decoder command was - * received - */ -bool -decoder_buffer_fill(struct decoder_buffer *buffer); - -/** - * Reads data from the buffer. This data is not yet consumed, you - * have to call decoder_buffer_consume() to do that. The returned - * buffer becomes invalid after a decoder_buffer_fill() or a - * decoder_buffer_consume() call. - * - * @param buffer the decoder_buffer object - * @param length_r pointer to a size_t where you will receive the - * number of bytes available - * @return a pointer to the read buffer, or NULL if there is no data - * available - */ -const void * -decoder_buffer_read(const struct decoder_buffer *buffer, size_t *length_r); - -/** - * Consume (delete, invalidate) a part of the buffer. The "nbytes" - * parameter must not be larger than the length returned by - * decoder_buffer_read(). - * - * @param buffer the decoder_buffer object - * @param nbytes the number of bytes to consume - */ -void -decoder_buffer_consume(struct decoder_buffer *buffer, size_t nbytes); - -/** - * Skips the specified number of bytes, discarding its data. - * - * @param buffer the decoder_buffer object - * @param nbytes the number of bytes to skip - * @return true on success, false on error - */ -bool -decoder_buffer_skip(struct decoder_buffer *buffer, size_t nbytes); - -#endif diff --git a/src/decoder_command.h b/src/decoder_command.h deleted file mode 100644 index 795e13fb2..000000000 --- a/src/decoder_command.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_COMMAND_H -#define MPD_DECODER_COMMAND_H - -enum decoder_command { - DECODE_COMMAND_NONE = 0, - DECODE_COMMAND_START, - DECODE_COMMAND_STOP, - DECODE_COMMAND_SEEK -}; - -#endif diff --git a/src/decoder_control.c b/src/decoder_control.c deleted file mode 100644 index 2ce03b666..000000000 --- a/src/decoder_control.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_control.h" -#include "pipe.h" - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_control" - -struct decoder_control * -dc_new(GCond *client_cond) -{ - struct decoder_control *dc = g_new(struct decoder_control, 1); - - dc->thread = NULL; - - dc->mutex = g_mutex_new(); - dc->cond = g_cond_new(); - dc->client_cond = client_cond; - - dc->state = DECODE_STATE_STOP; - dc->command = DECODE_COMMAND_NONE; - - dc->replay_gain_db = 0; - dc->replay_gain_prev_db = 0; - dc->mixramp_start = NULL; - dc->mixramp_end = NULL; - dc->mixramp_prev_end = NULL; - - return dc; -} - -void -dc_free(struct decoder_control *dc) -{ - g_cond_free(dc->cond); - g_mutex_free(dc->mutex); - g_free(dc->mixramp_start); - g_free(dc->mixramp_end); - g_free(dc->mixramp_prev_end); - g_free(dc); -} - -static void -dc_command_wait_locked(struct decoder_control *dc) -{ - while (dc->command != DECODE_COMMAND_NONE) - g_cond_wait(dc->client_cond, dc->mutex); -} - -static void -dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) -{ - dc->command = cmd; - decoder_signal(dc); - dc_command_wait_locked(dc); -} - -static void -dc_command(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - dc_command_locked(dc, cmd); - decoder_unlock(dc); -} - -static void -dc_command_async(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - - dc->command = cmd; - decoder_signal(dc); - - decoder_unlock(dc); -} - -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe) -{ - assert(song != NULL); - assert(buffer != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); - - dc->song = song; - dc->start_ms = start_ms; - dc->end_ms = end_ms; - dc->buffer = buffer; - dc->pipe = pipe; - dc_command(dc, DECODE_COMMAND_START); -} - -void -dc_stop(struct decoder_control *dc) -{ - 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_locked(dc, 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 decoder_control *dc, double where) -{ - assert(dc->state != DECODE_STATE_START); - assert(where >= 0.0); - - 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(dc, DECODE_COMMAND_SEEK); - - if (dc->seek_error) - return false; - - return true; -} - -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; -} - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_end); - dc->mixramp_end = mixramp_end; -} - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_prev_end); - dc->mixramp_prev_end = mixramp_prev_end; -} diff --git a/src/decoder_control.h b/src/decoder_control.h deleted file mode 100644 index 566b153ee..000000000 --- a/src/decoder_control.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_CONTROL_H -#define MPD_DECODER_CONTROL_H - -#include "decoder_command.h" -#include "audio_format.h" - -#include <glib.h> - -#include <assert.h> - -enum decoder_state { - DECODE_STATE_STOP = 0, - DECODE_STATE_START, - DECODE_STATE_DECODE, - - /** - * The last "START" command failed, because there was an I/O - * error or because no decoder was able to decode the file. - * This state will only come after START; once the state has - * turned to DECODE, by definition no such error can occur. - */ - DECODE_STATE_ERROR, -}; - -struct decoder_control { - /** the handle of the decoder thread, or NULL if the decoder - thread isn't running */ - GThread *thread; - - /** - * 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; - - /** - * The trigger of this object's client. It is signalled - * whenever an event occurs. - */ - GCond *client_cond; - - enum decoder_state state; - enum decoder_command command; - - bool quit; - bool seek_error; - bool seekable; - double seek_where; - - /** the format of the song file */ - struct audio_format in_audio_format; - - /** the format being sent to the music pipe */ - struct audio_format out_audio_format; - - /** - * 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; - - /** - * The initial seek position (in milliseconds), e.g. to the - * start of a sub-track described by a CUE file. - * - * This attribute is set by dc_start(). - */ - unsigned start_ms; - - /** - * The decoder will stop when it reaches this position (in - * milliseconds). 0 means don't stop before the end of the - * file. - * - * This attribute is set by dc_start(). - */ - unsigned end_ms; - - float total_time; - - /** the #music_chunk allocator */ - struct music_buffer *buffer; - - /** - * 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; -}; - -G_GNUC_MALLOC -struct decoder_control * -dc_new(GCond *client_cond); - -void -dc_free(struct decoder_control *dc); - -/** - * Locks the #decoder_control object. - */ -static inline void -decoder_lock(struct decoder_control *dc) -{ - g_mutex_lock(dc->mutex); -} - -/** - * Unlocks the #decoder_control object. - */ -static inline void -decoder_unlock(struct decoder_control *dc) -{ - g_mutex_unlock(dc->mutex); -} - -/** - * 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) -{ - g_cond_wait(dc->cond, dc->mutex); -} - -/** - * 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) -{ - 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; -} - -static inline bool -decoder_is_starting(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_START; -} - -static inline bool -decoder_has_failed(const struct decoder_control *dc) -{ - 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->song; - } - - assert(false); - return NULL; -} - -/** - * Start the decoder. - * - * @param the decoder - * @param song the song to be decoded - * @param start_ms see #decoder_control - * @param end_ms see #decoder_control - * @param pipe the pipe which receives the decoded chunks (owned by - * the caller) - */ -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe); - -void -dc_stop(struct decoder_control *dc); - -bool -dc_seek(struct decoder_control *dc, double where); - -void -dc_quit(struct decoder_control *dc); - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); - -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 deleted file mode 100644 index bc349f2ff..000000000 --- a/src/decoder_internal.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "pipe.h" -#include "input_stream.h" -#include "buffer.h" -#include "chunk.h" - -#include <assert.h> - -/** - * All chunks are full of decoded data; wait for the player to free - * one. - */ -static enum decoder_command -need_chunks(struct decoder_control *dc, bool do_wait) -{ - if (dc->command == DECODE_COMMAND_STOP || - dc->command == DECODE_COMMAND_SEEK) - return dc->command; - - if (do_wait) { - decoder_wait(dc); - g_cond_signal(dc->client_cond); - - return dc->command; - } - - return DECODE_COMMAND_NONE; -} - -struct music_chunk * -decoder_get_chunk(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - enum decoder_command cmd; - - assert(decoder != NULL); - - if (decoder->chunk != NULL) - return decoder->chunk; - - do { - 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; - } - - decoder_lock(dc); - cmd = need_chunks(dc, true); - decoder_unlock(dc); - } while (cmd == DECODE_COMMAND_NONE); - - return NULL; -} - -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); - else - music_pipe_push(dc->pipe, decoder->chunk); - - decoder->chunk = NULL; -} diff --git a/src/decoder_internal.h b/src/decoder_internal.h deleted file mode 100644 index d89e68cfc..000000000 --- a/src/decoder_internal.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_INTERNAL_H -#define MPD_DECODER_INTERNAL_H - -#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; - - /** - * Is the initial seek (to the start position of the sub-song) - * pending, or has it been performed already? - */ - bool initial_seek_pending; - - /** - * Is the initial seek currently running? During this time, - * the decoder command is SEEK. This flag is set by - * decoder_get_virtual_command(), when the virtual SEEK - * command is generated for the first time. - */ - bool initial_seek_running; - - /** - * This flag is set by decoder_seek_where(), and checked by - * decoder_command_finished(). It is used to clean up after - * seeking. - */ - bool seeking; - - /** - * The tag from the song object. This is only used for local - * files, because we expect the stream server to send us a new - * tag each time we play it. - */ - struct tag *song_tag; - - /** the last tag received from the stream */ - struct tag *stream_tag; - - /** the last tag received from the decoder plugin */ - struct tag *decoder_tag; - - /** 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; -}; - -/** - * Returns the current chunk the decoder writes to, or allocates a new - * chunk if there is none. - * - * @return the chunk, or NULL if we have received a decoder command - */ -struct music_chunk * -decoder_get_chunk(struct decoder *decoder); - -/** - * Flushes the current chunk. - */ -void -decoder_flush_chunk(struct decoder *decoder); - -#endif diff --git a/src/decoder_list.c b/src/decoder_list.c deleted file mode 100644 index 177b632ad..000000000 --- a/src/decoder_list.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "utils.h" -#include "conf.h" -#include "mpd_error.h" -#include "decoder/pcm_decoder_plugin.h" -#include "decoder/dsdiff_decoder_plugin.h" -#include "decoder/dsf_decoder_plugin.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; -extern const struct decoder_plugin mpcdec_decoder_plugin; -extern const struct decoder_plugin wavpack_decoder_plugin; -extern const struct decoder_plugin modplug_decoder_plugin; -extern const struct decoder_plugin mikmod_decoder_plugin; -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; - -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 -#if defined(HAVE_FLAC) - &oggflac_decoder_plugin, -#endif -#ifdef HAVE_FLAC - &flac_decoder_plugin, -#endif -#ifdef ENABLE_SNDFILE - &sndfile_decoder_plugin, -#endif -#ifdef HAVE_AUDIOFILE - &audiofile_decoder_plugin, -#endif - &dsdiff_decoder_plugin, - &dsf_decoder_plugin, -#ifdef HAVE_FAAD - &faad_decoder_plugin, -#endif -#ifdef HAVE_MP4 - &mp4ff_decoder_plugin, -#endif -#ifdef HAVE_MPCDEC - &mpcdec_decoder_plugin, -#endif -#ifdef HAVE_WAVPACK - &wavpack_decoder_plugin, -#endif -#ifdef HAVE_MODPLUG - &modplug_decoder_plugin, -#endif -#ifdef ENABLE_MIKMOD_DECODER - &mikmod_decoder_plugin, -#endif -#ifdef ENABLE_SIDPLAY - &sidplay_decoder_plugin, -#endif -#ifdef ENABLE_WILDMIDI - &wildmidi_decoder_plugin, -#endif -#ifdef ENABLE_FLUIDSYNTH - &fluidsynth_decoder_plugin, -#endif -#ifdef HAVE_FFMPEG - &ffmpeg_decoder_plugin, -#endif -#ifdef HAVE_GME - &gme_decoder_plugin, -#endif - &pcm_decoder_plugin, - NULL -}; - -enum { - num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, -}; - -/** which plugins have been initialized successfully? */ -bool decoder_plugins_enabled[num_decoder_plugins]; - -static unsigned -decoder_plugin_index(const struct decoder_plugin *plugin) -{ - 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; - - for (unsigned i = decoder_plugin_next_index(plugin); - decoder_plugins[i] != NULL; ++i) { - plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - decoder_plugin_supports_suffix(plugin, suffix)) - return plugin; - } - - return NULL; -} - -const struct decoder_plugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) -{ - static unsigned i = num_decoder_plugins; - - if (mimeType == NULL) - return NULL; - - if (!next) - i = 0; - for (; decoder_plugins[i] != NULL; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - decoder_plugin_supports_mime_type(plugin, mimeType)) { - ++i; - return plugin; - } - } - - return NULL; -} - -const struct decoder_plugin * -decoder_plugin_from_name(const char *name) -{ - decoder_plugins_for_each_enabled(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} - -/** - * Find the "decoder" configuration block for the specified plugin. - * - * @param plugin_name the name of the decoder plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -decoder_plugin_config(const char *plugin_name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) - MPD_ERROR("decoder configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -void decoder_plugin_init_all(void) -{ - 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); - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (decoder_plugin_init(plugin, param)) - decoder_plugins_enabled[i] = true; - } -} - -void decoder_plugin_deinit_all(void) -{ - decoder_plugins_for_each_enabled(plugin) - decoder_plugin_finish(plugin); -} diff --git a/src/decoder_list.h b/src/decoder_list.h deleted file mode 100644 index d0a6ade7e..000000000 --- a/src/decoder_list.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_LIST_H -#define MPD_DECODER_LIST_H - -#include <stdbool.h> - -struct decoder_plugin; - -extern const struct decoder_plugin *const decoder_plugins[]; -extern bool decoder_plugins_enabled[]; - -#define decoder_plugins_for_each(plugin) \ - for (const struct decoder_plugin *plugin, \ - *const*decoder_plugin_iterator = &decoder_plugins[0]; \ - (plugin = *decoder_plugin_iterator) != NULL; \ - ++decoder_plugin_iterator) - -#define decoder_plugins_for_each_enabled(plugin) \ - decoder_plugins_for_each(plugin) \ - if (decoder_plugins_enabled[decoder_plugin_iterator - decoder_plugins]) - -/* 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, - const struct decoder_plugin *plugin); - -const struct decoder_plugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); - -const struct decoder_plugin * -decoder_plugin_from_name(const char *name); - -/* this is where we "load" all the "plugins" ;-) */ -void decoder_plugin_init_all(void); - -/* this is where we "unload" all the "plugins" */ -void decoder_plugin_deinit_all(void); - -#endif diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c deleted file mode 100644 index d32043f0e..000000000 --- a/src/decoder_plugin.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_plugin.h" -#include "string_util.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 deleted file mode 100644 index 933ba6751..000000000 --- a/src/decoder_plugin.h +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_PLUGIN_H -#define MPD_DECODER_PLUGIN_H - -#include <stdbool.h> -#include <stddef.h> - -struct config_param; -struct input_stream; -struct tag; -struct tag_handler; - -/** - * Opaque handle which the decoder plugin passes to the functions in - * this header. - */ -struct decoder; - -struct decoder_plugin { - const char *name; - - /** - * Initialize the decoder 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 decoder plugin which was initialized - * successfully. Optional method. - */ - void (*finish)(void); - - /** - * Decode a stream (data read from an #input_stream object). - * - * Either implement this method or file_decode(). If - * possible, it is recommended to implement this method, - * because it is more versatile. - */ - void (*stream_decode)(struct decoder *decoder, - struct input_stream *is); - - /** - * Decode a local file. - * - * Either implement this method or stream_decode(). - */ - void (*file_decode)(struct decoder *decoder, const char *path_fs); - - /** - * Scan metadata of a file. - * - * @return false if the operation has failed - */ - bool (*scan_file)(const char *path_fs, - const struct tag_handler *handler, - void *handler_ctx); - - /** - * Scan metadata of a file. - * - * @return false if the operation has failed - */ - bool (*scan_stream)(struct input_stream *is, - const struct tag_handler *handler, - void *handler_ctx); - - /** - * @brief Return a "virtual" filename for subtracks in - * container formats like flac - * @param const char* pathname full pathname for the file on fs - * @param const unsigned int tnum track number - * - * @return NULL if there are no multiple files - * a filename for every single track according to tnum (param 2) - * do not include full pathname here, just the "virtual" file - */ - char* (*container_scan)(const char *path_fs, const unsigned int tnum); - - /* last element in these arrays must always be a NULL: */ - const char *const*suffixes; - const char *const*mime_types; -}; - -/** - * Initialize a decoder 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 -decoder_plugin_init(const struct decoder_plugin *plugin, - const struct config_param *param) -{ - return plugin->init != NULL - ? plugin->init(param) - : true; -} - -/** - * Deinitialize a decoder plugin which was initialized successfully. - */ -static inline void -decoder_plugin_finish(const struct decoder_plugin *plugin) -{ - if (plugin->finish != NULL) - plugin->finish(); -} - -/** - * Decode a stream. - */ -static inline void -decoder_plugin_stream_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, struct input_stream *is) -{ - plugin->stream_decode(decoder, is); -} - -/** - * Decode a file. - */ -static inline void -decoder_plugin_file_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, const char *path_fs) -{ - plugin->file_decode(decoder, path_fs); -} - -/** - * Read the tag of a file. - */ -static inline bool -decoder_plugin_scan_file(const struct decoder_plugin *plugin, - const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - return plugin->scan_file != NULL - ? plugin->scan_file(path_fs, handler, handler_ctx) - : false; -} - -/** - * Read the tag of a stream. - */ -static inline bool -decoder_plugin_scan_stream(const struct decoder_plugin *plugin, - struct input_stream *is, - const struct tag_handler *handler, - void *handler_ctx) -{ - return plugin->scan_stream != NULL - ? plugin->scan_stream(is, handler, handler_ctx) - : false; -} - -/** - * return "virtual" tracks in a container - */ -static inline char * -decoder_plugin_container_scan( const struct decoder_plugin *plugin, - const char* pathname, - const unsigned int tnum) -{ - 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 deleted file mode 100644 index e14477ed8..000000000 --- a/src/decoder_print.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_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) -{ - decoder_plugins_for_each_enabled(plugin) - decoder_plugin_print(client, plugin); -} diff --git a/src/decoder_print.h b/src/decoder_print.h deleted file mode 100644 index 31713d5d8..000000000 --- a/src/decoder_print.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index af80ed45b..000000000 --- a/src/decoder_thread.c +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_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 "pipe.h" -#include "song.h" -#include "tag.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_thread" - -/** - * Marks the current decoder command as "finished" and notifies the - * player thread. - * - * @param dc the #decoder_control object; must be locked - */ -static void -decoder_command_finished_locked(struct decoder_control *dc) -{ - assert(dc->command != DECODE_COMMAND_NONE); - - dc->command = DECODE_COMMAND_NONE; - - g_cond_signal(dc->client_cond); -} - -/** - * 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, dc->mutex, dc->cond, &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 */ - - decoder_lock(dc); - - input_stream_update(is); - while (!is->ready && - dc->command != DECODE_COMMAND_STOP) { - decoder_wait(dc); - - input_stream_update(is); - } - - if (!input_stream_check(is, &error)) { - decoder_unlock(dc); - - g_warning("%s", error->message); - g_error_free(error); - - return NULL; - } - - decoder_unlock(dc); - - return is; -} - -static bool -decoder_stream_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, - struct input_stream *input_stream) -{ - assert(plugin != NULL); - assert(plugin->stream_decode != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(input_stream != NULL); - assert(input_stream->ready); - assert(decoder->dc->state == DECODE_STATE_START); - - g_debug("probing plugin %s", plugin->name); - - if (decoder->dc->command == DECODE_COMMAND_STOP) - return true; - - /* rewind the stream, so each plugin gets a fresh start */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); - - decoder_unlock(decoder->dc); - - decoder_plugin_stream_decode(plugin, decoder, input_stream); - - decoder_lock(decoder->dc); - - assert(decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - - return decoder->dc->state != DECODE_STATE_START; -} - -static bool -decoder_file_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, const char *path) -{ - assert(plugin != NULL); - assert(plugin->file_decode != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(path != NULL); - assert(g_path_is_absolute(path)); - assert(decoder->dc->state == DECODE_STATE_START); - - g_debug("probing plugin %s", plugin->name); - - if (decoder->dc->command == DECODE_COMMAND_STOP) - return true; - - decoder_unlock(decoder->dc); - - decoder_plugin_file_decode(plugin, decoder, path); - - decoder_lock(decoder->dc); - - assert(decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - - return decoder->dc->state != DECODE_STATE_START; -} - -/** - * Hack to allow tracking const decoder plugins in a GSList. - */ -static inline gpointer -deconst_plugin(const struct decoder_plugin *plugin) -{ - 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; - - 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)); - } - - return false; -} - -/** - * 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); - - const char *suffix = uri_get_suffix(uri); - const struct decoder_plugin *plugin = NULL; - - if (suffix == NULL) - return false; - - 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)); - } - - 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; - } - - decoder_lock(dc); - - GSList *tried = NULL; - - success = dc->command == DECODE_COMMAND_STOP || - /* first we try mime types: */ - 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; - - decoder_lock(dc); - - 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, - .initial_seek_pending = dc->start_ms > 0, - .initial_seek_running = false, - }; - 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; - - decoder_command_finished_locked(dc); - - 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 (decoder.song_tag != NULL) - tag_free(decoder.song_tag); - - if (decoder.stream_tag != NULL) - tag_free(decoder.stream_tag); - - if (decoder.decoder_tag != NULL) - tag_free(decoder.decoder_tag); - - decoder_lock(dc); - - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; -} - -static void -decoder_run(struct decoder_control *dc) -{ - 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; - decoder_command_finished_locked(dc); - return; - } - - decoder_run_song(dc, song, uri); - g_free(uri); - -} - -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); - - switch (dc->command) { - case DECODE_COMMAND_START: - 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(dc); - break; - - case DECODE_COMMAND_STOP: - decoder_command_finished_locked(dc); - break; - - case DECODE_COMMAND_NONE: - decoder_wait(dc); - break; - } - } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); - - decoder_unlock(dc); - - return NULL; -} - -void -decoder_thread_start(struct decoder_control *dc) -{ - GError *e = NULL; - - assert(dc->thread == NULL); - - dc->quit = false; - - 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 deleted file mode 100644 index 78f12a54a..000000000 --- a/src/decoder_thread.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_THREAD_H -#define MPD_DECODER_THREAD_H - -struct decoder_control; - -void -decoder_thread_start(struct decoder_control *dc); - -#endif diff --git a/src/despotify_utils.c b/src/despotify_utils.c deleted file mode 100644 index 7555d105d..000000000 --- a/src/despotify_utils.c +++ /dev/null @@ -1,121 +0,0 @@ -#include <glib.h> -#include <despotify.h> - -#include "tag.h" -#include "conf.h" -#include "despotify_utils.h" - -static struct despotify_session *g_session; -static void (*registered_callbacks[8])(struct despotify_session *, - int, void *, void *); -static void *registered_callback_data[8]; - -static void callback(struct despotify_session* ds, int sig, - void* data, G_GNUC_UNUSED void* callback_data) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; - void *cb_data = registered_callback_data[i]; - - if (cb) - cb(ds, sig, data, cb_data); - } -} - -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - - if (!registered_callbacks[i]) { - registered_callbacks[i] = cb; - registered_callback_data[i] = cb_data; - - return true; - } - } - - return false; -} - -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - - if (registered_callbacks[i] == cb) { - registered_callbacks[i] = NULL; - } - } -} - - -struct tag *mpd_despotify_tag_from_track(struct ds_track *track) -{ - char tracknum[20]; - char comment[80]; - char date[20]; - struct tag *tag; - - tag = tag_new(); - - if (!track->has_meta_data) - return tag; - - g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); - g_snprintf(date, sizeof(date), "%d", track->year); - g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", - track->file_bitrate / 1000, track->geo_restricted ? "" : "not "); - tag_add_item(tag, TAG_TITLE, track->title); - tag_add_item(tag, TAG_ARTIST, track->artist->name); - tag_add_item(tag, TAG_TRACK, tracknum); - tag_add_item(tag, TAG_ALBUM, track->album); - tag_add_item(tag, TAG_DATE, date); - tag_add_item(tag, TAG_COMMENT, comment); - tag->time = track->length / 1000; - - return tag; -} - -struct despotify_session *mpd_despotify_get_session(void) -{ - const char *user; - const char *passwd; - bool high_bitrate; - - if (g_session) - return g_session; - - user = config_get_string(CONF_DESPOTIFY_USER, NULL); - passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL); - high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); - - if (user == NULL || passwd == NULL) { - g_debug("disabling despotify because account is not configured"); - return NULL; - } - if (!despotify_init()) { - g_debug("Can't initialize despotify\n"); - return false; - } - - g_session = despotify_init_client(callback, NULL, - high_bitrate, true); - if (!g_session) { - g_debug("Can't initialize despotify client\n"); - return false; - } - - if (!despotify_authenticate(g_session, user, passwd)) { - g_debug("Can't authenticate despotify session\n"); - despotify_exit(g_session); - return false; - } - - return g_session; -} diff --git a/src/despotify_utils.h b/src/despotify_utils.h deleted file mode 100644 index 7e35edc3c..000000000 --- a/src/despotify_utils.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DESPOTIFY_H -#define MPD_DESPOTIFY_H - -struct despotify_session; -struct ds_track; - -/** - * Return the current despotify session. - * - * If the session isn't initialized, this function will initialize - * it and connect to Spotify. - * - * @return a pointer to the despotify session, or NULL if it can't - * be initialized (e.g., if the configuration isn't supplied) - */ -struct despotify_session *mpd_despotify_get_session(void); - -/** - * Create a MPD tags structure from a spotify track - * - * @param track the track to convert - * - * @return a pointer to the filled in tags structure - */ -struct tag *mpd_despotify_tag_from_track(struct ds_track *track); - -/** - * Register a despotify callback. - * - * Despotify calls this e.g., when a track ends. - * - * @param cb the callback - * @param cb_data the data to pass to the callback - * - * @return true if the callback could be registered - */ -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data); - -/** - * Unregister a despotify callback. - * - * @param cb the callback to unregister. - */ -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); - -#endif - diff --git a/src/directory.c b/src/directory.c deleted file mode 100644 index e886698d6..000000000 --- a/src/directory.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "directory.h" -#include "song.h" -#include "song_sort.h" -#include "playlist_vector.h" -#include "path.h" -#include "util/list_sort.h" -#include "db_visitor.h" -#include "db_lock.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -struct directory * -directory_new(const char *path, struct directory *parent) -{ - struct directory *directory; - size_t pathlen = strlen(path); - - assert(path != NULL); - assert((*path == 0) == (parent == NULL)); - - directory = g_malloc0(sizeof(*directory) - - sizeof(directory->path) + pathlen + 1); - INIT_LIST_HEAD(&directory->children); - INIT_LIST_HEAD(&directory->songs); - INIT_LIST_HEAD(&directory->playlists); - - directory->parent = parent; - memcpy(directory->path, path, pathlen + 1); - - return directory; -} - -void -directory_free(struct directory *directory) -{ - playlist_vector_deinit(&directory->playlists); - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) - song_free(song); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) - directory_free(child); - - g_free(directory); - /* this resets last dir returned */ - /*directory_get_path(NULL); */ -} - -void -directory_delete(struct directory *directory) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(directory->parent != NULL); - - list_del(&directory->siblings); - directory_free(directory); -} - -const char * -directory_get_name(const struct directory *directory) -{ - assert(!directory_is_root(directory)); - assert(directory->path != NULL); - - const char *slash = strrchr(directory->path, '/'); - assert((slash == NULL) == directory_is_root(directory->parent)); - - return slash != NULL - ? slash + 1 - : directory->path; -} - -struct directory * -directory_new_child(struct directory *parent, const char *name_utf8) -{ - assert(holding_db_lock()); - assert(parent != NULL); - assert(name_utf8 != NULL); - assert(*name_utf8 != 0); - - char *allocated; - const char *path_utf8; - if (directory_is_root(parent)) { - allocated = NULL; - path_utf8 = name_utf8; - } else { - allocated = g_strconcat(directory_get_path(parent), - "/", name_utf8, NULL); - path_utf8 = allocated; - } - - struct directory *directory = directory_new(path_utf8, parent); - g_free(allocated); - - list_add_tail(&directory->siblings, &parent->children); - return directory; -} - -struct directory * -directory_get_child(const struct directory *directory, const char *name) -{ - assert(holding_db_lock()); - - struct directory *child; - directory_for_each_child(child, directory) - if (strcmp(directory_get_name(child), name) == 0) - return child; - - return NULL; -} - -void -directory_prune_empty(struct directory *directory) -{ - assert(holding_db_lock()); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - directory_prune_empty(child); - - if (directory_is_empty(child)) - directory_delete(child); - } -} - -struct directory * -directory_lookup_directory(struct directory *directory, const char *uri) -{ - assert(holding_db_lock()); - assert(uri != NULL); - - if (isRootDirectory(uri)) - return directory; - - char *duplicated = g_strdup(uri), *name = duplicated; - - while (1) { - char *slash = strchr(name, '/'); - if (slash == name) { - directory = NULL; - break; - } - - if (slash != NULL) - *slash = '\0'; - - directory = directory_get_child(directory, name); - if (directory == NULL || slash == NULL) - break; - - name = slash + 1; - } - - g_free(duplicated); - - return directory; -} - -void -directory_add_song(struct directory *directory, struct song *song) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(song != NULL); - assert(song->parent == directory); - - list_add_tail(&song->siblings, &directory->songs); -} - -void -directory_remove_song(G_GNUC_UNUSED struct directory *directory, - struct song *song) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(song != NULL); - assert(song->parent == directory); - - list_del(&song->siblings); -} - -struct song * -directory_get_song(const struct directory *directory, const char *name_utf8) -{ - assert(holding_db_lock()); - assert(directory != NULL); - assert(name_utf8 != NULL); - - struct song *song; - directory_for_each_song(song, directory) { - assert(song->parent == directory); - - if (strcmp(song->uri, name_utf8) == 0) - return song; - } - - return NULL; -} - -struct song * -directory_lookup_song(struct directory *directory, const char *uri) -{ - char *duplicated, *base; - - assert(holding_db_lock()); - assert(directory != NULL); - assert(uri != NULL); - - duplicated = g_strdup(uri); - base = strrchr(duplicated, '/'); - - if (base != NULL) { - *base++ = 0; - directory = directory_lookup_directory(directory, duplicated); - if (directory == NULL) { - g_free(duplicated); - return NULL; - } - } else - base = duplicated; - - struct song *song = directory_get_song(directory, base); - assert(song == NULL || song->parent == directory); - - g_free(duplicated); - return song; - -} - -static int -directory_cmp(G_GNUC_UNUSED void *priv, - struct list_head *_a, struct list_head *_b) -{ - const struct directory *a = (const struct directory *)_a; - const struct directory *b = (const struct directory *)_b; - return g_utf8_collate(a->path, b->path); -} - -void -directory_sort(struct directory *directory) -{ - assert(holding_db_lock()); - - list_sort(NULL, &directory->children, directory_cmp); - song_list_sort(&directory->songs); - - struct directory *child; - directory_for_each_child(child, directory) - directory_sort(child); -} - -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r) -{ - assert(directory != NULL); - assert(visitor != NULL); - assert(error_r == NULL || *error_r == NULL); - - if (visitor->song != NULL) { - struct song *song; - directory_for_each_song(song, directory) - if (!visitor->song(song, ctx, error_r)) - return false; - } - - if (visitor->playlist != NULL) { - struct playlist_metadata *i; - directory_for_each_playlist(i, directory) - if (!visitor->playlist(i, directory, ctx, error_r)) - return false; - } - - struct directory *child; - directory_for_each_child(child, directory) { - if (visitor->directory != NULL && - !visitor->directory(child, ctx, error_r)) - return false; - - if (recursive && - !directory_walk(child, recursive, visitor, ctx, error_r)) - return false; - } - - return true; -} diff --git a/src/directory.h b/src/directory.h deleted file mode 100644 index b3cd9c8c9..000000000 --- a/src/directory.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_H -#define MPD_DIRECTORY_H - -#include "check.h" -#include "util/list.h" - -#include <glib.h> -#include <stdbool.h> -#include <sys/types.h> - -#define DEVICE_INARCHIVE (dev_t)(-1) -#define DEVICE_CONTAINER (dev_t)(-2) - -#define directory_for_each_child(pos, directory) \ - list_for_each_entry(pos, &directory->children, siblings) - -#define directory_for_each_child_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->children, siblings) - -#define directory_for_each_song(pos, directory) \ - list_for_each_entry(pos, &directory->songs, siblings) - -#define directory_for_each_song_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->songs, siblings) - -#define directory_for_each_playlist(pos, directory) \ - list_for_each_entry(pos, &directory->playlists, siblings) - -#define directory_for_each_playlist_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &directory->playlists, siblings) - -struct song; -struct db_visitor; - -struct directory { - /** - * Pointers to the siblings of this directory within the - * parent directory. It is unused (undefined) in the root - * directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head siblings; - - /** - * A doubly linked list of child directories. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head children; - - /** - * A doubly linked list of songs within this directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head songs; - - struct list_head playlists; - - struct directory *parent; - time_t mtime; - ino_t inode; - dev_t device; - bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ - char path[sizeof(long)]; -}; - -static inline bool -isRootDirectory(const char *name) -{ - return name[0] == 0 || (name[0] == '/' && name[1] == 0); -} - -/** - * Generic constructor for #directory object. - */ -G_GNUC_MALLOC -struct directory * -directory_new(const char *dirname, struct directory *parent); - -/** - * Create a new root #directory object. - */ -G_GNUC_MALLOC -static inline struct directory * -directory_new_root(void) -{ - return directory_new("", NULL); -} - -/** - * Free this #directory object (and the whole object tree within it), - * assuming it was already removed from the parent. - */ -void -directory_free(struct directory *directory); - -/** - * Remove this #directory object from its parent and free it. This - * must not be called with the root directory. - * - * Caller must lock the #db_mutex. - */ -void -directory_delete(struct directory *directory); - -static inline bool -directory_is_empty(const struct directory *directory) -{ - return list_empty(&directory->children) && - list_empty(&directory->songs) && - list_empty(&directory->playlists); -} - -static inline const char * -directory_get_path(const struct directory *directory) -{ - return directory->path; -} - -/** - * Is this the root directory of the music database? - */ -static inline bool -directory_is_root(const struct directory *directory) -{ - return directory->parent == NULL; -} - -/** - * Returns the base name of the directory. - */ -G_GNUC_PURE -const char * -directory_get_name(const struct directory *directory); - -/** - * Caller must lock the #db_mutex. - */ -G_GNUC_PURE -struct directory * -directory_get_child(const struct directory *directory, const char *name); - -/** - * Create a new #directory object as a child of the given one. - * - * Caller must lock the #db_mutex. - * - * @param parent the parent directory the new one will be added to - * @param name_utf8 the UTF-8 encoded name of the new sub directory - */ -G_GNUC_MALLOC -struct directory * -directory_new_child(struct directory *parent, const char *name_utf8); - -/** - * Look up a sub directory, and create the object if it does not - * exist. - * - * Caller must lock the #db_mutex. - */ -static inline struct directory * -directory_make_child(struct directory *directory, const char *name_utf8) -{ - struct directory *child = directory_get_child(directory, name_utf8); - if (child == NULL) - child = directory_new_child(directory, name_utf8); - return child; -} - -/** - * Caller must lock the #db_mutex. - */ -void -directory_prune_empty(struct directory *directory); - -/** - * Looks up a directory by its relative URI. - * - * @param directory the parent (or grandparent, ...) directory - * @param uri the relative URI - * @return the directory, or NULL if none was found - */ -struct directory * -directory_lookup_directory(struct directory *directory, const char *uri); - -/** - * Add a song object to this directory. Its "parent" attribute must - * be set already. - */ -void -directory_add_song(struct directory *directory, struct song *song); - -/** - * Remove a song object from this directory (which effectively - * invalidates the song object, because the "parent" attribute becomes - * stale), but does not free it. - */ -void -directory_remove_song(struct directory *directory, struct song *song); - -/** - * Look up a song in this directory by its name. - * - * Caller must lock the #db_mutex. - */ -G_GNUC_PURE -struct song * -directory_get_song(const struct directory *directory, const char *name_utf8); - -/** - * Looks up a song by its relative URI. - * - * Caller must lock the #db_mutex. - * - * @param directory the parent (or grandparent, ...) directory - * @param uri the relative URI - * @return the song, or NULL if none was found - */ -struct song * -directory_lookup_song(struct directory *directory, const char *uri); - -/** - * Sort all directory entries recursively. - * - * Caller must lock the #db_mutex. - */ -void -directory_sort(struct directory *directory); - -/** - * Caller must lock #db_mutex. - */ -bool -directory_walk(const struct directory *directory, bool recursive, - const struct db_visitor *visitor, void *ctx, - GError **error_r); - -#endif diff --git a/src/directory_save.c b/src/directory_save.c deleted file mode 100644 index de1df050a..000000000 --- a/src/directory_save.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "directory_save.h" -#include "directory.h" -#include "song.h" -#include "text_file.h" -#include "song_save.h" -#include "playlist_database.h" - -#include <assert.h> -#include <string.h> - -#define DIRECTORY_DIR "directory: " -#define DIRECTORY_MTIME "mtime: " -#define DIRECTORY_BEGIN "begin: " -#define DIRECTORY_END "end: " - -/** - * The quark used for GError.domain. - */ -static inline GQuark -directory_quark(void) -{ - return g_quark_from_static_string("directory"); -} - -void -directory_save(FILE *fp, const struct directory *directory) -{ - if (!directory_is_root(directory)) { - fprintf(fp, DIRECTORY_MTIME "%lu\n", - (unsigned long)directory->mtime); - - fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, - directory_get_path(directory)); - } - - struct directory *cur; - directory_for_each_child(cur, directory) { - char *base = g_path_get_basename(cur->path); - - fprintf(fp, DIRECTORY_DIR "%s\n", base); - g_free(base); - - directory_save(fp, cur); - - if (ferror(fp)) - return; - } - - struct song *song; - directory_for_each_song(song, directory) - song_save(fp, song); - - playlist_vector_save(fp, &directory->playlists); - - if (!directory_is_root(directory)) - fprintf(fp, DIRECTORY_END "%s\n", - directory_get_path(directory)); -} - -static struct directory * -directory_load_subdir(FILE *fp, struct directory *parent, const char *name, - GString *buffer, GError **error_r) -{ - const char *line; - bool success; - - if (directory_get_child(parent, name) != NULL) { - g_set_error(error_r, directory_quark(), 0, - "Duplicate subdirectory '%s'", name); - return NULL; - } - - struct directory *directory = directory_new_child(parent, name); - - line = read_text_line(fp, buffer); - if (line == NULL) { - g_set_error(error_r, directory_quark(), 0, - "Unexpected end of file"); - directory_delete(directory); - return NULL; - } - - 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_delete(directory); - return NULL; - } - } - - if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { - g_set_error(error_r, directory_quark(), 0, - "Malformed line: %s", line); - directory_delete(directory); - return NULL; - } - - success = directory_load(fp, directory, buffer, error_r); - if (!success) { - directory_delete(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; - } else if (g_str_has_prefix(line, SONG_BEGIN)) { - const char *name = line + sizeof(SONG_BEGIN) - 1; - struct song *song; - - if (directory_get_song(directory, name) != NULL) { - g_set_error(error, directory_quark(), 0, - "Duplicate song '%s'", name); - return NULL; - } - - song = song_load(fp, directory, name, - buffer, error); - if (song == NULL) - return false; - - directory_add_song(directory, song); - } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { - /* duplicate the name, because - playlist_metadata_load() will overwrite the - 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; - } - - g_free(name); - } else { - g_set_error(error, directory_quark(), 0, - "Malformed line: %s", line); - return false; - } - } - - return true; -} diff --git a/src/directory_save.h b/src/directory_save.h deleted file mode 100644 index 2d0056830..000000000 --- a/src/directory_save.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_SAVE_H -#define MPD_DIRECTORY_SAVE_H - -#include <glib.h> - -#include <stdbool.h> -#include <stdio.h> - -struct directory; - -void -directory_save(FILE *fp, const struct directory *directory); - -bool -directory_load(FILE *fp, struct directory *directory, - GString *buffer, GError **error); - -#endif diff --git a/src/dsd2pcm/dsd2pcm.hpp b/src/dsd2pcm/dsd2pcm.hpp deleted file mode 100644 index b1b2ae1c5..000000000 --- a/src/dsd2pcm/dsd2pcm.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef DSD2PCM_HXX_INCLUDED -#define DSD2PCM_HXX_INCLUDED - -#include <algorithm> -#include <stdexcept> -#include "dsd2pcm.h" - -/** - * C++ PImpl Wrapper for the dsd2pcm C library - */ - -class dxd -{ - dsd2pcm_ctx *handle; -public: - dxd() : handle(dsd2pcm_init()) - { if (!handle) throw std::runtime_error("wtf?!"); } - - dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) - { if (!handle) throw std::runtime_error("wtf?!"); } - - ~dxd() { dsd2pcm_destroy(handle); } - - friend void swap(dxd & a, dxd & b) - { std::swap(a.handle,b.handle); } - - dxd& operator=(dxd x) - { swap(*this,x); return *this; } - - void translate(size_t samples, - const unsigned char *src, ptrdiff_t src_stride, - bool lsbitfirst, - float *dst, ptrdiff_t dst_stride) - { - dsd2pcm_translate(handle,samples,src,src_stride, - lsbitfirst,dst,dst_stride); - } -}; - -#endif // DSD2PCM_HXX_INCLUDED - diff --git a/src/dsd2pcm/noiseshape.hpp b/src/dsd2pcm/noiseshape.hpp deleted file mode 100644 index 726272f91..000000000 --- a/src/dsd2pcm/noiseshape.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef NOISE_SHAPE_HXX_INCLUDED -#define NOISE_SHAPE_HXX_INCLUDED - -#include <stdexcept> -#include "noiseshape.h" - -/** - * C++ wrapper for the noiseshape C library - */ - -class noise_shaper -{ - noise_shape_ctx ctx; -public: - noise_shaper(int sos_count, const float *bbaa) - { - if (noise_shape_init(&ctx,sos_count,bbaa)) - throw std::runtime_error("noise shaper initialization failed"); - } - - noise_shaper(noise_shaper const& x) - { - if (noise_shape_clone(&x.ctx,&ctx)) - throw std::runtime_error("noise shaper initialization failed"); - } - - ~noise_shaper() - { noise_shape_destroy(&ctx); } - - noise_shaper& operator=(noise_shaper const& x) - { - if (this != &x) { - noise_shape_destroy(&ctx); - if (noise_shape_clone(&x.ctx,&ctx)) - throw std::runtime_error("noise shaper initialization failed"); - } - return *this; - } - - float get() { return noise_shape_get(&ctx); } - - void update(float error) { noise_shape_update(&ctx,error); } -}; - -#endif /* NOISE_SHAPE_HXX_INCLUDED */ - diff --git a/src/dummy.cxx b/src/dummy.cxx deleted file mode 100644 index b21555d06..000000000 --- a/src/dummy.cxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Just a dummy C++ file that is linked to work around an automake - * bug: automake uses CXXLD when at least one source is C++, but when - * you link a static library with a C++ source, it uses CCLD. This - * causes linker problems (undefined reference to 'operator - * delete(void*)'), because CCLD does not link with libstdc++. - * - * By linking with this empty C++ source, automake decides to use - * CXXLD. - * - */ diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx new file mode 100644 index 000000000..5a77e24a7 --- /dev/null +++ b/src/encoder/FlacEncoderPlugin.cxx @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FlacEncoderPlugin.hxx" +#include "EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "pcm/PcmBuffer.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/fifo_buffer.h" + +extern "C" { +#include "util/growing_fifo.h" +} + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#include <FLAC/stream_encoder.h> + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + +struct flac_encoder { + Encoder encoder; + + AudioFormat audio_format; + unsigned compression; + + FLAC__StreamEncoder *fse; + + PcmBuffer expand_buffer; + + /** + * This buffer will hold encoded data from libFLAC until it is + * picked up with flac_encoder_read(). + */ + struct fifo_buffer *output_buffer; + + flac_encoder():encoder(flac_encoder_plugin) {} +}; + +static constexpr Domain flac_encoder_domain("vorbis_encoder"); + +static bool +flac_encoder_configure(struct flac_encoder *encoder, const config_param ¶m, + gcc_unused Error &error) +{ + encoder->compression = param.GetBlockValue("compression", 5u); + + return true; +} + +static Encoder * +flac_encoder_init(const config_param ¶m, Error &error) +{ + flac_encoder *encoder = new flac_encoder(); + + /* load configuration from "param" */ + if (!flac_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +flac_encoder_finish(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 */ + delete encoder; +} + +static bool +flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, + Error &error) +{ + if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, + encoder->compression)) { + error.Format(config_domain, + "error setting flac compression to %d", + encoder->compression); + return false; + } + + if ( !FLAC__stream_encoder_set_channels(encoder->fse, + encoder->audio_format.channels)) { + error.Format(config_domain, + "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)) { + error.Format(config_domain, + "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)) { + error.Format(config_domain, + "error setting flac sample rate to %d", + encoder->audio_format.sample_rate); + return false; + } + return true; +} + +static FLAC__StreamEncoderWriteStatus +flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse, + const FLAC__byte data[], + size_t bytes, + gcc_unused unsigned samples, + gcc_unused unsigned current_frame, void *client_data) +{ + struct flac_encoder *encoder = (struct flac_encoder *) client_data; + + //transfer data to buffer + growing_fifo_append(&encoder->output_buffer, data, bytes); + + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + +static void +flac_encoder_close(Encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + FLAC__stream_encoder_delete(encoder->fse); + + encoder->expand_buffer.Clear(); + fifo_buffer_free(encoder->output_buffer); +} + +static bool +flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &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 SampleFormat::S8: + bits_per_sample = 8; + break; + + case SampleFormat::S16: + bits_per_sample = 16; + break; + + case SampleFormat::S24_P32: + bits_per_sample = 24; + break; + + default: + bits_per_sample = 24; + audio_format.format = SampleFormat::S24_P32; + } + + /* allocate the encoder */ + encoder->fse = FLAC__stream_encoder_new(); + if (encoder->fse == nullptr) { + error.Set(flac_encoder_domain, "flac_new() failed"); + return false; + } + + if (!flac_encoder_setup(encoder, bits_per_sample, error)) { + FLAC__stream_encoder_delete(encoder->fse); + return false; + } + + encoder->output_buffer = growing_fifo_new(); + + /* this immediately outputs data through callback */ + + { + FLAC__StreamEncoderInitStatus init_status; + + init_status = FLAC__stream_encoder_init_stream(encoder->fse, + flac_write_callback, + nullptr, nullptr, nullptr, encoder); + + if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + error.Format(flac_encoder_domain, + "failed to initialize encoder: %s\n", + FLAC__StreamEncoderInitStatusString[init_status]); + flac_encoder_close(_encoder); + return false; + } + } + + return true; +} + + +static bool +flac_encoder_flush(Encoder *_encoder, gcc_unused Error &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(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned num_frames, num_samples; + void *exbuffer; + const void *buffer = nullptr; + + /* format conversion */ + + num_frames = length / encoder->audio_format.GetFrameSize(); + num_samples = num_frames * encoder->audio_format.channels; + + switch (encoder->audio_format.format) { + case SampleFormat::S8: + exbuffer = encoder->expand_buffer.Get(length * 4); + pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data, + num_samples); + buffer = exbuffer; + break; + + case SampleFormat::S16: + exbuffer = encoder->expand_buffer.Get(length * 2); + pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data, + num_samples); + buffer = exbuffer; + break; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + /* nothing need to be done; format is the same for + both mpd and libFLAC */ + buffer = data; + break; + + default: + gcc_unreachable(); + } + + /* feed samples to encoder */ + + if (!FLAC__stream_encoder_process_interleaved(encoder->fse, + (const FLAC__int32 *)buffer, + num_frames)) { + error.Set(flac_encoder_domain, "flac encoder process failed"); + return false; + } + + return true; +} + +static size_t +flac_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + size_t max_length; + const char *src = (const char *) + fifo_buffer_read(encoder->output_buffer, &max_length); + if (src == nullptr) + return 0; + + if (length > max_length) + length = max_length; + + memcpy(dest, src, length); + fifo_buffer_consume(encoder->output_buffer, length); + return length; +} + +static const char * +flac_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/flac"; +} + +const EncoderPlugin flac_encoder_plugin = { + "flac", + flac_encoder_init, + flac_encoder_finish, + flac_encoder_open, + flac_encoder_close, + flac_encoder_flush, + flac_encoder_flush, + nullptr, + nullptr, + flac_encoder_write, + flac_encoder_read, + flac_encoder_get_mime_type, +}; + diff --git a/src/encoder/FlacEncoderPlugin.hxx b/src/encoder/FlacEncoderPlugin.hxx new file mode 100644 index 000000000..928a7f93e --- /dev/null +++ b/src/encoder/FlacEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_FLAC_HXX +#define MPD_ENCODER_FLAC_HXX + +extern const struct EncoderPlugin flac_encoder_plugin; + +#endif diff --git a/src/encoder/LameEncoderPlugin.cxx b/src/encoder/LameEncoderPlugin.cxx new file mode 100644 index 000000000..a5b7be483 --- /dev/null +++ b/src/encoder/LameEncoderPlugin.cxx @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "LameEncoderPlugin.hxx" +#include "EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "ConfigError.hxx" +#include "util/ReusableArray.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <lame/lame.h> + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct LameEncoder final { + Encoder encoder; + + AudioFormat audio_format; + float quality; + int bitrate; + + lame_global_flags *gfp; + + Manual<ReusableArray<unsigned char, 32768>> output_buffer; + unsigned char *output_begin, *output_end; + + LameEncoder():encoder(lame_encoder_plugin) {} + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain lame_encoder_domain("lame_encoder"); + +bool +LameEncoder::Configure(const config_param ¶m, Error &error) +{ + const char *value; + char *endptr; + + value = param.GetBlockValue("quality"); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + quality = g_ascii_strtod(value, &endptr); + + if (*endptr != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are both defined"); + return false; + } + } else { + /* a bit rate was configured */ + + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + quality = -2.0; + bitrate = g_ascii_strtoll(value, &endptr, 10); + + if (*endptr != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate should be a positive integer"); + return false; + } + } + + return true; +} + +static Encoder * +lame_encoder_init(const config_param ¶m, Error &error) +{ + LameEncoder *encoder = new LameEncoder(); + + /* load configuration from "param" */ + if (!encoder->Configure(param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +lame_encoder_finish(Encoder *_encoder) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + /* the real liblame cleanup was already performed by + lame_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +lame_encoder_setup(LameEncoder *encoder, Error &error) +{ + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) { + error.Set(lame_encoder_domain, + "error setting lame VBR mode"); + return false; + } + if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) { + error.Set(lame_encoder_domain, + "error setting lame VBR quality"); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) { + error.Set(lame_encoder_domain, + "error setting lame bitrate"); + return false; + } + } + + if (0 != lame_set_num_channels(encoder->gfp, + encoder->audio_format.channels)) { + error.Set(lame_encoder_domain, + "error setting lame num channels"); + return false; + } + + if (0 != lame_set_in_samplerate(encoder->gfp, + encoder->audio_format.sample_rate)) { + error.Set(lame_encoder_domain, + "error setting lame sample rate"); + return false; + } + + if (0 != lame_set_out_samplerate(encoder->gfp, + encoder->audio_format.sample_rate)) { + error.Set(lame_encoder_domain, + "error setting lame out sample rate"); + return false; + } + + if (0 > lame_init_params(encoder->gfp)) { + error.Set(lame_encoder_domain, + "error initializing lame params"); + return false; + } + + return true; +} + +static bool +lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + audio_format.format = SampleFormat::S16; + audio_format.channels = 2; + + encoder->audio_format = audio_format; + + encoder->gfp = lame_init(); + if (encoder->gfp == nullptr) { + error.Set(lame_encoder_domain, "lame_init() failed"); + return false; + } + + if (!lame_encoder_setup(encoder, error)) { + lame_close(encoder->gfp); + return false; + } + + encoder->output_buffer.Construct(); + encoder->output_begin = encoder->output_end = nullptr; + + return true; +} + +static void +lame_encoder_close(Encoder *_encoder) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + lame_close(encoder->gfp); + encoder->output_buffer.Destruct(); +} + +static bool +lame_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + const int16_t *src = (const int16_t*)data; + + assert(encoder->output_begin == encoder->output_end); + + const unsigned num_frames = + length / encoder->audio_format.GetFrameSize(); + const unsigned num_samples = + length / encoder->audio_format.GetSampleSize(); + + /* worst-case formula according to LAME documentation */ + const size_t output_buffer_size = 5 * num_samples / 4 + 7200; + const auto output_buffer = encoder->output_buffer->Get(output_buffer_size); + + /* this is for only 16-bit audio */ + + int bytes_out = lame_encode_buffer_interleaved(encoder->gfp, + const_cast<short *>(src), + num_frames, + output_buffer, + output_buffer_size); + + if (bytes_out < 0) { + error.Set(lame_encoder_domain, "lame encoder failed"); + return false; + } + + encoder->output_begin = output_buffer; + encoder->output_end = output_buffer + bytes_out; + return true; +} + +static size_t +lame_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + const auto begin = encoder->output_begin; + assert(begin <= encoder->output_end); + const size_t remainning = encoder->output_end - begin; + if (length > remainning) + length = remainning; + + memcpy(dest, begin, length); + + encoder->output_begin = begin + length; + return length; +} + +static const char * +lame_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/mpeg"; +} + +const EncoderPlugin lame_encoder_plugin = { + "lame", + lame_encoder_init, + lame_encoder_finish, + lame_encoder_open, + lame_encoder_close, + nullptr, + nullptr, + nullptr, + nullptr, + lame_encoder_write, + lame_encoder_read, + lame_encoder_get_mime_type, +}; diff --git a/src/encoder/LameEncoderPlugin.hxx b/src/encoder/LameEncoderPlugin.hxx new file mode 100644 index 000000000..49832baee --- /dev/null +++ b/src/encoder/LameEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_LAME_HXX +#define MPD_ENCODER_LAME_HXX + +extern const struct EncoderPlugin lame_encoder_plugin; + +#endif diff --git a/src/encoder/NullEncoderPlugin.cxx b/src/encoder/NullEncoderPlugin.cxx new file mode 100644 index 000000000..38bc5cbe3 --- /dev/null +++ b/src/encoder/NullEncoderPlugin.cxx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "NullEncoderPlugin.hxx" +#include "EncoderAPI.hxx" +#include "util/fifo_buffer.h" +extern "C" { +#include "util/growing_fifo.h" +} +#include "gcc.h" + +#include <assert.h> +#include <string.h> + +struct NullEncoder final { + Encoder encoder; + + struct fifo_buffer *buffer; + + NullEncoder():encoder(null_encoder_plugin) {} +}; + +static Encoder * +null_encoder_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + NullEncoder *encoder = new NullEncoder(); + return &encoder->encoder; +} + +static void +null_encoder_finish(Encoder *_encoder) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + delete encoder; +} + +static void +null_encoder_close(Encoder *_encoder) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + fifo_buffer_free(encoder->buffer); +} + + +static bool +null_encoder_open(Encoder *_encoder, + gcc_unused AudioFormat &audio_format, + gcc_unused Error &error) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + encoder->buffer = growing_fifo_new(); + return true; +} + +static bool +null_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + growing_fifo_append(&encoder->buffer, data, length); + return length; +} + +static size_t +null_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + size_t max_length; + const void *src = fifo_buffer_read(encoder->buffer, &max_length); + if (src == nullptr) + return 0; + + if (length > max_length) + length = max_length; + + memcpy(dest, src, length); + fifo_buffer_consume(encoder->buffer, length); + return length; +} + +const EncoderPlugin null_encoder_plugin = { + "null", + null_encoder_init, + null_encoder_finish, + null_encoder_open, + null_encoder_close, + nullptr, + nullptr, + nullptr, + nullptr, + null_encoder_write, + null_encoder_read, + nullptr, +}; diff --git a/src/encoder/NullEncoderPlugin.hxx b/src/encoder/NullEncoderPlugin.hxx new file mode 100644 index 000000000..b741a2f6d --- /dev/null +++ b/src/encoder/NullEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_NULL_HXX +#define MPD_ENCODER_NULL_HXX + +extern const struct EncoderPlugin null_encoder_plugin; + +#endif diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx new file mode 100644 index 000000000..ce847f491 --- /dev/null +++ b/src/encoder/OggStream.hxx @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OGG_STREAM_HXX +#define MPD_OGG_STREAM_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <assert.h> +#include <string.h> +#include <stdint.h> + +class OggStream { + ogg_stream_state state; + + bool flush; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + OggStream():initialized(false) {} + ~OggStream() { + assert(!initialized); + } +#endif + + void Initialize(int serialno) { + assert(!initialized); + + ogg_stream_init(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Reinitialize(int serialno) { + assert(initialized); + + ogg_stream_reset_serialno(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + } + + void Deinitialize() { + assert(initialized); + + ogg_stream_clear(&state); + +#ifndef NDEBUG + initialized = false; +#endif + } + + void Flush() { + assert(initialized); + + flush = true; + } + + void PacketIn(const ogg_packet &packet) { + assert(initialized); + + ogg_stream_packetin(&state, + const_cast<ogg_packet *>(&packet)); + } + + bool PageOut(ogg_page &page) { + int result = ogg_stream_pageout(&state, &page); + if (result == 0 && flush) { + flush = false; + result = ogg_stream_flush(&state, &page); + } + + return result != 0; + } + + size_t PageOut(void *_buffer, size_t size) { + ogg_page page; + if (!PageOut(page)) + return 0; + + assert(page.header_len > 0 || page.body_len > 0); + + size_t header_len = (size_t)page.header_len; + size_t body_len = (size_t)page.body_len; + assert(header_len <= size); + + if (header_len + body_len > size) + /* TODO: better overflow handling */ + body_len = size - header_len; + + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(buffer, page.header, header_len); + memcpy(buffer + header_len, page.body, body_len); + + return header_len + body_len; + } +}; + +#endif diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx new file mode 100644 index 000000000..f3803e2ec --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.cxx @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusEncoderPlugin.hxx" +#include "OggStream.hxx" +#include "EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <assert.h> + +struct opus_encoder { + /** the base class */ + Encoder encoder; + + /* configuration */ + + opus_int32 bitrate; + int complexity; + int signal; + + /* runtime information */ + + AudioFormat audio_format; + + size_t frame_size; + + size_t buffer_frames, buffer_size, buffer_position; + uint8_t *buffer; + + OpusEncoder *enc; + + unsigned char buffer2[1275 * 3 + 7]; + + OggStream stream; + + int lookahead; + + ogg_int64_t packetno; + + ogg_int64_t granulepos; + + opus_encoder():encoder(opus_encoder_plugin) {} +}; + +static constexpr Domain opus_encoder_domain("opus_encoder"); + +static bool +opus_encoder_configure(struct opus_encoder *encoder, + const config_param ¶m, Error &error) +{ + const char *value = param.GetBlockValue("bitrate", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "max") == 0) + encoder->bitrate = OPUS_BITRATE_MAX; + else { + char *endptr; + encoder->bitrate = strtoul(value, &endptr, 10); + if (endptr == value || *endptr != 0 || + encoder->bitrate < 500 || encoder->bitrate > 512000) { + error.Set(config_domain, "Invalid bit rate"); + return false; + } + } + + encoder->complexity = param.GetBlockValue("complexity", 10u); + if (encoder->complexity > 10) { + error.Format(config_domain, "Invalid complexity"); + return false; + } + + value = param.GetBlockValue("signal", "auto"); + if (strcmp(value, "auto") == 0) + encoder->signal = OPUS_AUTO; + else if (strcmp(value, "voice") == 0) + encoder->signal = OPUS_SIGNAL_VOICE; + else if (strcmp(value, "music") == 0) + encoder->signal = OPUS_SIGNAL_MUSIC; + else { + error.Format(config_domain, "Invalid signal"); + return false; + } + + return true; +} + +static Encoder * +opus_encoder_init(const config_param ¶m, Error &error) +{ + opus_encoder *encoder = new opus_encoder(); + + /* load configuration from "param" */ + if (!opus_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return NULL; + } + + return &encoder->encoder; +} + +static void +opus_encoder_finish(Encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* the real libopus cleanup was already performed by + opus_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +opus_encoder_open(Encoder *_encoder, + AudioFormat &audio_format, + Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* libopus supports only 48 kHz */ + audio_format.sample_rate = 48000; + + if (audio_format.channels > 2) + audio_format.channels = 1; + + switch (audio_format.format) { + case SampleFormat::S16: + case SampleFormat::FLOAT: + break; + + case SampleFormat::S8: + audio_format.format = SampleFormat::S16; + break; + + default: + audio_format.format = SampleFormat::FLOAT; + break; + } + + encoder->audio_format = audio_format; + encoder->frame_size = audio_format.GetFrameSize(); + + int error_code; + encoder->enc = opus_encoder_create(audio_format.sample_rate, + audio_format.channels, + OPUS_APPLICATION_AUDIO, + &error_code); + if (encoder->enc == nullptr) { + error.Set(opus_encoder_domain, error_code, + opus_strerror(error_code)); + return false; + } + + opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate)); + opus_encoder_ctl(encoder->enc, + OPUS_SET_COMPLEXITY(encoder->complexity)); + opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal)); + + opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead)); + + encoder->buffer_frames = audio_format.sample_rate / 50; + encoder->buffer_size = encoder->frame_size * encoder->buffer_frames; + encoder->buffer_position = 0; + encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size); + + encoder->stream.Initialize(g_random_int()); + encoder->packetno = 0; + + return true; +} + +static void +opus_encoder_close(Encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Deinitialize(); + g_free(encoder->buffer); + opus_encoder_destroy(encoder->enc); +} + +static bool +opus_encoder_do_encode(struct opus_encoder *encoder, bool eos, + Error &error) +{ + assert(encoder->buffer_position == encoder->buffer_size); + + opus_int32 result = + encoder->audio_format.format == SampleFormat::S16 + ? opus_encode(encoder->enc, + (const opus_int16 *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)) + : opus_encode_float(encoder->enc, + (const float *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)); + if (result < 0) { + error.Set(opus_encoder_domain, "Opus encoder error"); + return false; + } + + encoder->granulepos += encoder->buffer_frames; + + ogg_packet packet; + packet.packet = encoder->buffer2; + packet.bytes = result; + packet.b_o_s = false; + packet.e_o_s = eos; + packet.granulepos = encoder->granulepos; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + + encoder->buffer_position = 0; + + return true; +} + +static bool +opus_encoder_end(Encoder *_encoder, Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + + memset(encoder->buffer + encoder->buffer_position, 0, + encoder->buffer_size - encoder->buffer_position); + encoder->buffer_position = encoder->buffer_size; + + return opus_encoder_do_encode(encoder, true, error); +} + +static bool +opus_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames, + Error &error) +{ + size_t fill_bytes = fill_frames * encoder->frame_size; + + while (fill_bytes > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > fill_bytes) + nbytes = fill_bytes; + + memset(encoder->buffer + encoder->buffer_position, + 0, nbytes); + encoder->buffer_position += nbytes; + fill_bytes -= nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error)) + return false; + } + + return true; +} + +static bool +opus_encoder_write(Encoder *_encoder, + const void *_data, size_t length, + Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + const uint8_t *data = (const uint8_t *)_data; + + if (encoder->lookahead > 0) { + /* generate some silence at the beginning of the + stream */ + + assert(encoder->buffer_position == 0); + + if (!opus_encoder_write_silence(encoder, encoder->lookahead, + error)) + return false; + + encoder->lookahead = 0; + } + + while (length > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > length) + nbytes = length; + + memcpy(encoder->buffer + encoder->buffer_position, + data, nbytes); + data += nbytes; + length -= nbytes; + encoder->buffer_position += nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error)) + return false; + } + + return true; +} + +static void +opus_encoder_generate_head(struct opus_encoder *encoder) +{ + unsigned char header[19]; + memcpy(header, "OpusHead", 8); + header[8] = 1; + header[9] = encoder->audio_format.channels; + *(uint16_t *)(header + 10) = GUINT16_TO_LE(encoder->lookahead); + *(uint32_t *)(header + 12) = + GUINT32_TO_LE(encoder->audio_format.sample_rate); + header[16] = 0; + header[17] = 0; + header[18] = 0; + + ogg_packet packet; + packet.packet = header; + packet.bytes = 19; + packet.b_o_s = true; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); +} + +static void +opus_encoder_generate_tags(struct opus_encoder *encoder) +{ + const char *version = opus_get_version_string(); + size_t version_length = strlen(version); + + size_t comments_size = 8 + 4 + version_length + 4; + unsigned char *comments = (unsigned char *)g_malloc(comments_size); + memcpy(comments, "OpusTags", 8); + *(uint32_t *)(comments + 8) = GUINT32_TO_LE(version_length); + memcpy(comments + 12, version, version_length); + *(uint32_t *)(comments + 12 + version_length) = GUINT32_TO_LE(0); + + ogg_packet packet; + packet.packet = comments; + packet.bytes = comments_size; + packet.b_o_s = false; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); + + g_free(comments); +} + +static size_t +opus_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + if (encoder->packetno == 0) + opus_encoder_generate_head(encoder); + else if (encoder->packetno == 1) + opus_encoder_generate_tags(encoder); + + return encoder->stream.PageOut(dest, length); +} + +static const char * +opus_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/ogg"; +} + +const EncoderPlugin opus_encoder_plugin = { + "opus", + opus_encoder_init, + opus_encoder_finish, + opus_encoder_open, + opus_encoder_close, + opus_encoder_end, + opus_encoder_flush, + nullptr, + nullptr, + opus_encoder_write, + opus_encoder_read, + opus_encoder_get_mime_type, +}; diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx new file mode 100644 index 000000000..d6da0e960 --- /dev/null +++ b/src/encoder/OpusEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ENCODER_OPUS_H +#define MPD_ENCODER_OPUS_H + +extern const struct EncoderPlugin opus_encoder_plugin; + +#endif diff --git a/src/encoder/TwolameEncoderPlugin.cxx b/src/encoder/TwolameEncoderPlugin.cxx new file mode 100644 index 000000000..6862173f7 --- /dev/null +++ b/src/encoder/TwolameEncoderPlugin.cxx @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TwolameEncoderPlugin.hxx" +#include "EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <twolame.h> + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct TwolameEncoder final { + Encoder encoder; + + AudioFormat audio_format; + float quality; + int bitrate; + + twolame_options *options; + + unsigned char output_buffer[32768]; + size_t output_buffer_length; + size_t output_buffer_position; + + /** + * Call libtwolame's flush function when the output_buffer is + * empty? + */ + bool flush; + + TwolameEncoder():encoder(twolame_encoder_plugin) {} + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain twolame_encoder_domain("twolame_encoder"); + +bool +TwolameEncoder::Configure(const config_param ¶m, Error &error) +{ + const char *value; + char *endptr; + + value = param.GetBlockValue("quality"); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + quality = g_ascii_strtod(value, &endptr); + + if (*endptr != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are both defined"); + return false; + } + } else { + /* a bit rate was configured */ + + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + quality = -2.0; + bitrate = g_ascii_strtoll(value, &endptr, 10); + + if (*endptr != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate should be a positive integer"); + return false; + } + } + + return true; +} + +static Encoder * +twolame_encoder_init(const config_param ¶m, Error &error_r) +{ + FormatDebug(twolame_encoder_domain, + "libtwolame version %s", get_twolame_version()); + + TwolameEncoder *encoder = new TwolameEncoder(); + + /* load configuration from "param" */ + if (!encoder->Configure(param, error_r)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +twolame_encoder_finish(Encoder *_encoder) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + /* the real libtwolame cleanup was already performed by + twolame_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +twolame_encoder_setup(TwolameEncoder *encoder, Error &error) +{ + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != twolame_set_VBR(encoder->options, true)) { + error.Set(twolame_encoder_domain, + "error setting twolame VBR mode"); + return false; + } + if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) { + error.Set(twolame_encoder_domain, + "error setting twolame VBR quality"); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) { + error.Set(twolame_encoder_domain, + "error setting twolame bitrate"); + return false; + } + } + + if (0 != twolame_set_num_channels(encoder->options, + encoder->audio_format.channels)) { + error.Set(twolame_encoder_domain, + "error setting twolame num channels"); + return false; + } + + if (0 != twolame_set_in_samplerate(encoder->options, + encoder->audio_format.sample_rate)) { + error.Set(twolame_encoder_domain, + "error setting twolame sample rate"); + return false; + } + + if (0 > twolame_init_params(encoder->options)) { + error.Set(twolame_encoder_domain, + "error initializing twolame params"); + return false; + } + + return true; +} + +static bool +twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, + Error &error) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + audio_format.format = SampleFormat::S16; + audio_format.channels = 2; + + encoder->audio_format = audio_format; + + encoder->options = twolame_init(); + if (encoder->options == nullptr) { + error.Set(twolame_encoder_domain, "twolame_init() failed"); + return false; + } + + if (!twolame_encoder_setup(encoder, error)) { + twolame_close(&encoder->options); + return false; + } + + encoder->output_buffer_length = 0; + encoder->output_buffer_position = 0; + encoder->flush = false; + + return true; +} + +static void +twolame_encoder_close(Encoder *_encoder) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + twolame_close(&encoder->options); +} + +static bool +twolame_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + encoder->flush = true; + return true; +} + +static bool +twolame_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + const int16_t *src = (const int16_t*)data; + + assert(encoder->output_buffer_position == + encoder->output_buffer_length); + + const unsigned num_frames = + length / encoder->audio_format.GetFrameSize(); + + int bytes_out = twolame_encode_buffer_interleaved(encoder->options, + src, num_frames, + encoder->output_buffer, + sizeof(encoder->output_buffer)); + if (bytes_out < 0) { + error.Set(twolame_encoder_domain, "twolame encoder failed"); + return false; + } + + encoder->output_buffer_length = (size_t)bytes_out; + encoder->output_buffer_position = 0; + return true; +} + +static size_t +twolame_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + assert(encoder->output_buffer_position <= + encoder->output_buffer_length); + + if (encoder->output_buffer_position == encoder->output_buffer_length && + encoder->flush) { + int ret = twolame_encode_flush(encoder->options, + encoder->output_buffer, + sizeof(encoder->output_buffer)); + if (ret > 0) { + encoder->output_buffer_length = (size_t)ret; + encoder->output_buffer_position = 0; + } + + encoder->flush = false; + } + + + const size_t remainning = encoder->output_buffer_length + - encoder->output_buffer_position; + if (length > remainning) + length = remainning; + + memcpy(dest, encoder->output_buffer + encoder->output_buffer_position, + length); + + encoder->output_buffer_position += length; + + return length; +} + +static const char * +twolame_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/mpeg"; +} + +const EncoderPlugin twolame_encoder_plugin = { + "twolame", + twolame_encoder_init, + twolame_encoder_finish, + twolame_encoder_open, + twolame_encoder_close, + twolame_encoder_flush, + twolame_encoder_flush, + nullptr, + nullptr, + twolame_encoder_write, + twolame_encoder_read, + twolame_encoder_get_mime_type, +}; diff --git a/src/encoder/TwolameEncoderPlugin.hxx b/src/encoder/TwolameEncoderPlugin.hxx new file mode 100644 index 000000000..dd8a536f6 --- /dev/null +++ b/src/encoder/TwolameEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_TWOLAME_HXX +#define MPD_ENCODER_TWOLAME_HXX + +extern const struct EncoderPlugin twolame_encoder_plugin; + +#endif diff --git a/src/encoder/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx new file mode 100644 index 000000000..84b4cac28 --- /dev/null +++ b/src/encoder/VorbisEncoderPlugin.cxx @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisEncoderPlugin.hxx" +#include "OggStream.hxx" +#include "EncoderAPI.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <vorbis/vorbisenc.h> + +#include <glib.h> + +#include <assert.h> + +struct vorbis_encoder { + /** the base class */ + Encoder encoder; + + /* configuration */ + + float quality; + int bitrate; + + /* runtime information */ + + AudioFormat audio_format; + + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_info vi; + + OggStream stream; + + vorbis_encoder():encoder(vorbis_encoder_plugin) {} +}; + +static constexpr Domain vorbis_encoder_domain("vorbis_encoder"); + +static bool +vorbis_encoder_configure(struct vorbis_encoder *encoder, + const config_param ¶m, Error &error) +{ + const char *value = param.GetBlockValue("quality"); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + char *endptr; + encoder->quality = g_ascii_strtod(value, &endptr); + + if (*endptr != '\0' || encoder->quality < -1.0 || + encoder->quality > 10.0) { + error.Format(config_domain, + "quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are both defined"); + return false; + } + } else { + /* a bit rate was configured */ + + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + encoder->quality = -2.0; + + char *endptr; + encoder->bitrate = g_ascii_strtoll(value, &endptr, 10); + if (*endptr != '\0' || encoder->bitrate <= 0) { + error.Set(config_domain, + "bitrate should be a positive integer"); + return false; + } + } + + return true; +} + +static Encoder * +vorbis_encoder_init(const config_param ¶m, Error &error) +{ + vorbis_encoder *encoder = new vorbis_encoder(); + + /* load configuration from "param" */ + if (!vorbis_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +vorbis_encoder_finish(Encoder *_encoder) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + /* the real libvorbis/libogg cleanup was already performed by + vorbis_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +vorbis_encoder_reinit(struct vorbis_encoder *encoder, Error &error) +{ + vorbis_info_init(&encoder->vi); + + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != vorbis_encode_init_vbr(&encoder->vi, + encoder->audio_format.channels, + encoder->audio_format.sample_rate, + encoder->quality * 0.1)) { + error.Set(vorbis_encoder_domain, + "error initializing vorbis vbr"); + vorbis_info_clear(&encoder->vi); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != vorbis_encode_init(&encoder->vi, + encoder->audio_format.channels, + encoder->audio_format.sample_rate, -1.0, + encoder->bitrate * 1000, -1.0)) { + error.Set(vorbis_encoder_domain, + "error initializing vorbis encoder"); + vorbis_info_clear(&encoder->vi); + return false; + } + } + + vorbis_analysis_init(&encoder->vd, &encoder->vi); + vorbis_block_init(&encoder->vd, &encoder->vb); + encoder->stream.Initialize(g_random_int()); + + return true; +} + +static void +vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) +{ + ogg_packet packet, comments, codebooks; + + vorbis_analysis_headerout(&encoder->vd, vc, + &packet, &comments, &codebooks); + + encoder->stream.PacketIn(packet); + encoder->stream.PacketIn(comments); + encoder->stream.PacketIn(codebooks); +} + +static void +vorbis_encoder_send_header(struct vorbis_encoder *encoder) +{ + vorbis_comment vc; + + vorbis_comment_init(&vc); + vorbis_encoder_headerout(encoder, &vc); + vorbis_comment_clear(&vc); +} + +static bool +vorbis_encoder_open(Encoder *_encoder, + AudioFormat &audio_format, + Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + audio_format.format = SampleFormat::FLOAT; + + encoder->audio_format = audio_format; + + if (!vorbis_encoder_reinit(encoder, error)) + return false; + + vorbis_encoder_send_header(encoder); + + return true; +} + +static void +vorbis_encoder_clear(struct vorbis_encoder *encoder) +{ + encoder->stream.Deinitialize(); + vorbis_block_clear(&encoder->vb); + vorbis_dsp_clear(&encoder->vd); + vorbis_info_clear(&encoder->vi); +} + +static void +vorbis_encoder_close(Encoder *_encoder) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + vorbis_encoder_clear(encoder); +} + +static void +vorbis_encoder_blockout(struct vorbis_encoder *encoder) +{ + while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { + vorbis_analysis(&encoder->vb, nullptr); + vorbis_bitrate_addblock(&encoder->vb); + + ogg_packet packet; + while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) + encoder->stream.PacketIn(packet); + } +} + +static bool +vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + vorbis_analysis_wrote(&encoder->vd, 0); + vorbis_encoder_blockout(encoder); + + /* reinitialize vorbis_dsp_state and vorbis_block to reset the + end-of-stream marker */ + vorbis_block_clear(&encoder->vb); + vorbis_dsp_clear(&encoder->vd); + vorbis_analysis_init(&encoder->vd, &encoder->vi); + vorbis_block_init(&encoder->vd, &encoder->vb); + + encoder->stream.Flush(); + return true; +} + +static void +copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag *tag) +{ + for (unsigned i = 0; i < tag->num_items; i++) { + const TagItem &item = *tag->items[i]; + char *name = g_ascii_strup(tag_item_names[item.type], -1); + vorbis_comment_add_tag(vc, name, item.value); + g_free(name); + } +} + +static bool +vorbis_encoder_tag(Encoder *_encoder, const Tag *tag, + gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + vorbis_comment comment; + + /* write the vorbis_comment object */ + + vorbis_comment_init(&comment); + copy_tag_to_vorbis_comment(&comment, tag); + + /* reset ogg_stream_state and begin a new stream */ + + encoder->stream.Reinitialize(g_random_int()); + + /* send that vorbis_comment to the ogg_stream_state */ + + vorbis_encoder_headerout(encoder, &comment); + vorbis_comment_clear(&comment); + + return true; +} + +static void +interleaved_to_vorbis_buffer(float **dest, const float *src, + unsigned num_frames, unsigned num_channels) +{ + for (unsigned i = 0; i < num_frames; i++) + for (unsigned j = 0; j < num_channels; j++) + dest[j][i] = *src++; +} + +static bool +vorbis_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + unsigned num_frames = length / encoder->audio_format.GetFrameSize(); + + /* this is for only 16-bit audio */ + + interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, + num_frames), + (const float *)data, + num_frames, + encoder->audio_format.channels); + + vorbis_analysis_wrote(&encoder->vd, num_frames); + vorbis_encoder_blockout(encoder); + return true; +} + +static size_t +vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + return encoder->stream.PageOut(dest, length); +} + +static const char * +vorbis_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/ogg"; +} + +const EncoderPlugin vorbis_encoder_plugin = { + "vorbis", + vorbis_encoder_init, + vorbis_encoder_finish, + vorbis_encoder_open, + vorbis_encoder_close, + vorbis_encoder_pre_tag, + vorbis_encoder_flush, + vorbis_encoder_pre_tag, + vorbis_encoder_tag, + vorbis_encoder_write, + vorbis_encoder_read, + vorbis_encoder_get_mime_type, +}; diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx new file mode 100644 index 000000000..72cc44f5c --- /dev/null +++ b/src/encoder/VorbisEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ENCODER_VORBIS_H +#define MPD_ENCODER_VORBIS_H + +extern const struct EncoderPlugin vorbis_encoder_plugin; + +#endif diff --git a/src/encoder/WaveEncoderPlugin.cxx b/src/encoder/WaveEncoderPlugin.cxx new file mode 100644 index 000000000..493b07b61 --- /dev/null +++ b/src/encoder/WaveEncoderPlugin.cxx @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "WaveEncoderPlugin.hxx" +#include "EncoderAPI.hxx" +#include "util/fifo_buffer.h" +extern "C" { +#include "util/growing_fifo.h" +} + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct WaveEncoder { + Encoder encoder; + unsigned bits; + + struct fifo_buffer *buffer; + + WaveEncoder():encoder(wave_encoder_plugin) {} +}; + +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; +}; + +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 Encoder * +wave_encoder_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + WaveEncoder *encoder = new WaveEncoder(); + return &encoder->encoder; +} + +static void +wave_encoder_finish(Encoder *_encoder) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + g_free(encoder); +} + +static bool +wave_encoder_open(Encoder *_encoder, + AudioFormat &audio_format, + gcc_unused Error &error) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + assert(audio_format.IsValid()); + + switch (audio_format.format) { + case SampleFormat::S8: + encoder->bits = 8; + break; + + case SampleFormat::S16: + encoder->bits = 16; + break; + + case SampleFormat::S24_P32: + encoder->bits = 24; + break; + + case SampleFormat::S32: + encoder->bits = 32; + break; + + default: + audio_format.format = SampleFormat::S16; + encoder->bits = 16; + break; + } + + encoder->buffer = growing_fifo_new(); + wave_header *header = (wave_header *) + growing_fifo_write(&encoder->buffer, sizeof(*header)); + + /* create PCM wave header in initial buffer */ + fill_wave_header(header, + audio_format.channels, + encoder->bits, + audio_format.sample_rate, + (encoder->bits / 8) * audio_format.channels); + fifo_buffer_append(encoder->buffer, sizeof(*header)); + + return true; +} + +static void +wave_encoder_close(Encoder *_encoder) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + fifo_buffer_free(encoder->buffer); +} + +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(Encoder *_encoder, + const void *src, size_t length, + gcc_unused Error &error) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + uint8_t *dst = (uint8_t *)growing_fifo_write(&encoder->buffer, 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, (const uint32_t *)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, (const uint16_t *)src, length); + break; + case 24: + length = pcm24_to_wave(dst, (const uint32_t *)src, length); + break; + case 32: + length = pcm32_to_wave(dst, (const uint32_t *)src, length); + break; + } +#else +#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder +#endif + + fifo_buffer_append(encoder->buffer, length); + return true; +} + +static size_t +wave_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + size_t max_length; + const void *src = fifo_buffer_read(encoder->buffer, &max_length); + if (src == NULL) + return 0; + + if (length > max_length) + length = max_length; + + memcpy(dest, src, length); + fifo_buffer_consume(encoder->buffer, length); + return length; +} + +static const char * +wave_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/wav"; +} + +const EncoderPlugin wave_encoder_plugin = { + "wave", + wave_encoder_init, + wave_encoder_finish, + wave_encoder_open, + wave_encoder_close, + nullptr, + nullptr, + nullptr, + nullptr, + wave_encoder_write, + wave_encoder_read, + wave_encoder_get_mime_type, +}; diff --git a/src/encoder/WaveEncoderPlugin.hxx b/src/encoder/WaveEncoderPlugin.hxx new file mode 100644 index 000000000..190ee131e --- /dev/null +++ b/src/encoder/WaveEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ENCODER_WAVE_HXX +#define MPD_ENCODER_WAVE_HXX + +extern const struct EncoderPlugin wave_encoder_plugin; + +#endif diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c deleted file mode 100644 index e32588e29..000000000 --- a/src/encoder/flac_encoder.c +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_api.h" -#include "encoder_plugin.h" -#include "audio_format.h" -#include "pcm_buffer.h" -#include "fifo_buffer.h" -#include "growing_fifo.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; - - /** - * This buffer will hold encoded data from libFLAC until it is - * picked up with flac_encoder_read(). - */ - struct fifo_buffer *output_buffer; -}; - -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; - - //transfer data to buffer - growing_fifo_append(&encoder->output_buffer, data, 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->expand_buffer); - fifo_buffer_free(encoder->output_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; - } - - pcm_buffer_init(&encoder->expand_buffer); - - encoder->output_buffer = growing_fifo_new(); - - /* this immediately outputs data through 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; - - size_t max_length; - const char *src = fifo_buffer_read(encoder->output_buffer, - &max_length); - if (src == NULL) - return 0; - - if (length > max_length) - length = max_length; - - memcpy(dest, src, length); - fifo_buffer_consume(encoder->output_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, - .end = flac_encoder_flush, - .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 deleted file mode 100644 index 3bb99ea28..000000000 --- a/src/encoder/lame_encoder.c +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_api.h" -#include "encoder_plugin.h" -#include "audio_format.h" - -#include <lame/lame.h> -#include <assert.h> -#include <string.h> - -struct lame_encoder { - struct encoder encoder; - - struct audio_format audio_format; - float quality; - int bitrate; - - lame_global_flags *gfp; - - unsigned char buffer[32768]; - size_t buffer_length; -}; - -extern const struct encoder_plugin lame_encoder_plugin; - -static inline GQuark -lame_encoder_quark(void) -{ - return g_quark_from_static_string("lame_encoder"); -} - -static bool -lame_encoder_configure(struct lame_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, lame_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, lame_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, lame_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, lame_encoder_quark(), 0, - "bitrate at line %i should be a positive integer", - param->line); - return false; - } - } - - return true; -} - -static struct encoder * -lame_encoder_init(const struct config_param *param, GError **error) -{ - struct lame_encoder *encoder; - - encoder = g_new(struct lame_encoder, 1); - encoder_struct_init(&encoder->encoder, &lame_encoder_plugin); - - /* load configuration from "param" */ - if (!lame_encoder_configure(encoder, param, error)) { - /* configuration has failed, roll back and return error */ - g_free(encoder); - return NULL; - } - - return &encoder->encoder; -} - -static void -lame_encoder_finish(struct encoder *_encoder) -{ - struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - - /* the real liblame cleanup was already performed by - lame_encoder_close(), so no real work here */ - g_free(encoder); -} - -static bool -lame_encoder_setup(struct lame_encoder *encoder, GError **error) -{ - if (encoder->quality >= -1.0) { - /* a quality was configured (VBR) */ - - if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) { - g_set_error(error, lame_encoder_quark(), 0, - "error setting lame VBR mode"); - return false; - } - if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) { - g_set_error(error, lame_encoder_quark(), 0, - "error setting lame VBR quality"); - return false; - } - } else { - /* a bit rate was configured */ - - if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) { - g_set_error(error, lame_encoder_quark(), 0, - "error setting lame bitrate"); - return false; - } - } - - if (0 != lame_set_num_channels(encoder->gfp, - encoder->audio_format.channels)) { - g_set_error(error, lame_encoder_quark(), 0, - "error setting lame num channels"); - return false; - } - - if (0 != lame_set_in_samplerate(encoder->gfp, - encoder->audio_format.sample_rate)) { - g_set_error(error, lame_encoder_quark(), 0, - "error setting lame sample rate"); - return false; - } - - if (0 != lame_set_out_samplerate(encoder->gfp, - encoder->audio_format.sample_rate)) { - g_set_error(error, lame_encoder_quark(), 0, - "error setting lame out sample rate"); - return false; - } - - if (0 > lame_init_params(encoder->gfp)) { - g_set_error(error, lame_encoder_quark(), 0, - "error initializing lame params"); - return false; - } - - return true; -} - -static bool -lame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, - GError **error) -{ - struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - - audio_format->format = SAMPLE_FORMAT_S16; - audio_format->channels = 2; - - encoder->audio_format = *audio_format; - - encoder->gfp = lame_init(); - if (encoder->gfp == NULL) { - g_set_error(error, lame_encoder_quark(), 0, - "lame_init() failed"); - return false; - } - - if (!lame_encoder_setup(encoder, error)) { - lame_close(encoder->gfp); - return false; - } - - encoder->buffer_length = 0; - - return true; -} - -static void -lame_encoder_close(struct encoder *_encoder) -{ - struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - - lame_close(encoder->gfp); -} - -static bool -lame_encoder_write(struct encoder *_encoder, - const void *data, size_t length, - G_GNUC_UNUSED GError **error) -{ - struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - unsigned num_frames; - float *left, *right; - const int16_t *src = (const int16_t*)data; - unsigned int i; - int bytes_out; - - assert(encoder->buffer_length == 0); - - num_frames = - length / audio_format_frame_size(&encoder->audio_format); - left = g_malloc(sizeof(left[0]) * num_frames); - right = g_malloc(sizeof(right[0]) * num_frames); - - /* this is for only 16-bit audio */ - - for (i = 0; i < num_frames; i++) { - left[i] = *src++; - right[i] = *src++; - } - - bytes_out = lame_encode_buffer_float(encoder->gfp, left, right, - num_frames, encoder->buffer, - sizeof(encoder->buffer)); - - g_free(left); - g_free(right); - - if (bytes_out < 0) { - g_set_error(error, lame_encoder_quark(), 0, - "lame encoder failed"); - return false; - } - - encoder->buffer_length = (size_t)bytes_out; - return true; -} - -static size_t -lame_encoder_read(struct encoder *_encoder, void *dest, size_t length) -{ - struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - - 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 * -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, - .finish = lame_encoder_finish, - .open = lame_encoder_open, - .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 deleted file mode 100644 index 48cdf139b..000000000 --- a/src/encoder/null_encoder.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_api.h" -#include "encoder_plugin.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" - -#include <assert.h> -#include <string.h> - -struct null_encoder { - struct encoder encoder; - - struct fifo_buffer *buffer; -}; - -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; - - fifo_buffer_free(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 = growing_fifo_new(); - 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; - - growing_fifo_append(&encoder->buffer, data, length); - return length; -} - -static size_t -null_encoder_read(struct encoder *_encoder, void *dest, size_t length) -{ - struct null_encoder *encoder = (struct null_encoder *)_encoder; - - size_t max_length; - const void *src = fifo_buffer_read(encoder->buffer, &max_length); - if (src == NULL) - return 0; - - if (length > max_length) - length = max_length; - - memcpy(dest, src, length); - fifo_buffer_consume(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 deleted file mode 100644 index 934b2ab24..000000000 --- a/src/encoder/twolame_encoder.c +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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, - .end = twolame_encoder_flush, - .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 deleted file mode 100644 index 468cf38ee..000000000 --- a/src/encoder/vorbis_encoder.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_api.h" -#include "encoder_plugin.h" -#include "tag.h" -#include "audio_format.h" -#include "mpd_error.h" - -#include <vorbis/vorbisenc.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "vorbis_encoder" - -struct vorbis_encoder { - /** the base class */ - struct encoder encoder; - - /* configuration */ - - float quality; - int bitrate; - - /* runtime information */ - - struct audio_format audio_format; - - ogg_stream_state os; - - vorbis_dsp_state vd; - vorbis_block vb; - vorbis_info vi; - - bool flush; -}; - -extern const struct encoder_plugin vorbis_encoder_plugin; - -static inline GQuark -vorbis_encoder_quark(void) -{ - return g_quark_from_static_string("vorbis_encoder"); -} - -static bool -vorbis_encoder_configure(struct vorbis_encoder *encoder, - const struct config_param *param, GError **error) -{ - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { - /* a quality was configured (VBR) */ - - char *endptr; - encoder->quality = g_ascii_strtod(value, &endptr); - - if (*endptr != '\0' || encoder->quality < -1.0 || - encoder->quality > 10.0) { - g_set_error(error, vorbis_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, vorbis_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, vorbis_encoder_quark(), 0, - "neither bitrate nor quality defined " - "at line %i", - param->line); - return false; - } - - encoder->quality = -2.0; - - char *endptr; - encoder->bitrate = g_ascii_strtoll(value, &endptr, 10); - if (*endptr != '\0' || encoder->bitrate <= 0) { - g_set_error(error, vorbis_encoder_quark(), 0, - "bitrate at line %i should be a positive integer", - param->line); - return false; - } - } - - return true; -} - -static struct encoder * -vorbis_encoder_init(const struct config_param *param, GError **error) -{ - struct vorbis_encoder *encoder = g_new(struct vorbis_encoder, 1); - encoder_struct_init(&encoder->encoder, &vorbis_encoder_plugin); - - /* load configuration from "param" */ - if (!vorbis_encoder_configure(encoder, param, error)) { - /* configuration has failed, roll back and return error */ - g_free(encoder); - return NULL; - } - - return &encoder->encoder; -} - -static void -vorbis_encoder_finish(struct encoder *_encoder) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - /* the real libvorbis/libogg cleanup was already performed by - vorbis_encoder_close(), so no real work here */ - g_free(encoder); -} - -static bool -vorbis_encoder_reinit(struct vorbis_encoder *encoder, GError **error) -{ - vorbis_info_init(&encoder->vi); - - if (encoder->quality >= -1.0) { - /* a quality was configured (VBR) */ - - if (0 != vorbis_encode_init_vbr(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, - encoder->quality * 0.1)) { - g_set_error(error, vorbis_encoder_quark(), 0, - "error initializing vorbis vbr"); - vorbis_info_clear(&encoder->vi); - return false; - } - } else { - /* a bit rate was configured */ - - if (0 != vorbis_encode_init(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, -1.0, - encoder->bitrate * 1000, -1.0)) { - g_set_error(error, vorbis_encoder_quark(), 0, - "error initializing vorbis encoder"); - vorbis_info_clear(&encoder->vi); - return false; - } - } - - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - ogg_stream_init(&encoder->os, g_random_int()); - - return true; -} - -static void -vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) -{ - ogg_packet packet, comments, codebooks; - - vorbis_analysis_headerout(&encoder->vd, vc, - &packet, &comments, &codebooks); - - ogg_stream_packetin(&encoder->os, &packet); - ogg_stream_packetin(&encoder->os, &comments); - ogg_stream_packetin(&encoder->os, &codebooks); -} - -static void -vorbis_encoder_send_header(struct vorbis_encoder *encoder) -{ - vorbis_comment vc; - - vorbis_comment_init(&vc); - vorbis_encoder_headerout(encoder, &vc); - vorbis_comment_clear(&vc); -} - -static bool -vorbis_encoder_open(struct encoder *_encoder, - struct audio_format *audio_format, - GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - audio_format->format = SAMPLE_FORMAT_S16; - - encoder->audio_format = *audio_format; - - if (!vorbis_encoder_reinit(encoder, error)) - return false; - - vorbis_encoder_send_header(encoder); - - /* set "flush" to true, so the caller gets the full headers on - the first read() */ - encoder->flush = true; - - return true; -} - -static void -vorbis_encoder_clear(struct vorbis_encoder *encoder) -{ - ogg_stream_clear(&encoder->os); - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_info_clear(&encoder->vi); -} - -static void -vorbis_encoder_close(struct encoder *_encoder) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - vorbis_encoder_clear(encoder); -} - -static void -vorbis_encoder_blockout(struct vorbis_encoder *encoder) -{ - while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, NULL); - vorbis_bitrate_addblock(&encoder->vb); - - ogg_packet packet; - while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - ogg_stream_packetin(&encoder->os, &packet); - } -} - -static bool -vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - encoder->flush = true; - return true; -} - -static bool -vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - vorbis_analysis_wrote(&encoder->vd, 0); - vorbis_encoder_blockout(encoder); - - /* reinitialize vorbis_dsp_state and vorbis_block to reset the - end-of-stream marker */ - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - - encoder->flush = true; - return true; -} - -static void -copy_tag_to_vorbis_comment(vorbis_comment *vc, const struct tag *tag) -{ - for (unsigned i = 0; i < tag->num_items; i++) { - struct tag_item *item = tag->items[i]; - char *name = g_ascii_strup(tag_item_names[item->type], -1); - vorbis_comment_add_tag(vc, name, item->value); - g_free(name); - } -} - -static bool -vorbis_encoder_tag(struct encoder *_encoder, const struct tag *tag, - G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - vorbis_comment comment; - - /* write the vorbis_comment object */ - - vorbis_comment_init(&comment); - copy_tag_to_vorbis_comment(&comment, tag); - - /* reset ogg_stream_state and begin a new stream */ - - ogg_stream_reset_serialno(&encoder->os, g_random_int()); - - /* send that vorbis_comment to the ogg_stream_state */ - - vorbis_encoder_headerout(encoder, &comment); - vorbis_comment_clear(&comment); - - /* the next vorbis_encoder_read() call should flush the - ogg_stream_state */ - - encoder->flush = true; - - return true; -} - -static void -pcm16_to_vorbis_buffer(float **dest, const int16_t *src, - unsigned num_frames, unsigned num_channels) -{ - for (unsigned i = 0; i < num_frames; i++) - for (unsigned j = 0; j < num_channels; j++) - dest[j][i] = *src++ / 32768.0; -} - -static bool -vorbis_encoder_write(struct encoder *_encoder, - const void *data, size_t length, - G_GNUC_UNUSED GError **error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - unsigned num_frames = length - / audio_format_frame_size(&encoder->audio_format); - - /* this is for only 16-bit audio */ - - pcm16_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, - num_frames), - (const int16_t *)data, - num_frames, encoder->audio_format.channels); - - vorbis_analysis_wrote(&encoder->vd, num_frames); - vorbis_encoder_blockout(encoder); - return true; -} - -static size_t -vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - unsigned char *dest = _dest; - - ogg_page page; - int ret = ogg_stream_pageout(&encoder->os, &page); - if (ret == 0 && encoder->flush) { - encoder->flush = false; - ret = ogg_stream_flush(&encoder->os, &page); - - } - - if (ret == 0) - return 0; - - assert(page.header_len > 0 || page.body_len > 0); - - size_t nbytes = (size_t)page.header_len + (size_t)page.body_len; - - if (nbytes > length) - /* XXX better error handling */ - MPD_ERROR("buffer too small"); - - memcpy(dest, page.header, page.header_len); - memcpy(dest + page.header_len, page.body, page.body_len); - - 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, - .finish = vorbis_encoder_finish, - .open = vorbis_encoder_open, - .close = vorbis_encoder_close, - .end = vorbis_encoder_pre_tag, - .flush = vorbis_encoder_flush, - .pre_tag = vorbis_encoder_pre_tag, - .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 deleted file mode 100644 index 9eeb4d513..000000000 --- a/src/encoder/wave_encoder.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_api.h" -#include "encoder_plugin.h" -#include "fifo_buffer.h" -#include "growing_fifo.h" - -#include <assert.h> -#include <string.h> - -struct wave_encoder { - struct encoder encoder; - unsigned bits; - - struct fifo_buffer *buffer; -}; - -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); - - return &encoder->encoder; -} - -static void -wave_encoder_finish(struct encoder *_encoder) -{ - struct wave_encoder *encoder = (struct wave_encoder *)_encoder; - - 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; - - 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; - } - - encoder->buffer = growing_fifo_new(); - struct wave_header *header = - growing_fifo_write(&encoder->buffer, sizeof(*header)); - - /* create PCM wave header in initial buffer */ - fill_wave_header(header, - audio_format->channels, - encoder->bits, - audio_format->sample_rate, - (encoder->bits / 8) * audio_format->channels ); - fifo_buffer_append(encoder->buffer, sizeof(*header)); - - return true; -} - -static void -wave_encoder_close(struct encoder *_encoder) -{ - struct wave_encoder *encoder = (struct wave_encoder *)_encoder; - - fifo_buffer_free(encoder->buffer); -} - -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 = growing_fifo_write(&encoder->buffer, 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 - - fifo_buffer_append(encoder->buffer, 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; - - size_t max_length; - const void *src = fifo_buffer_read(encoder->buffer, &max_length); - if (src == NULL) - return 0; - - if (length > max_length) - length = max_length; - - memcpy(dest, src, length); - fifo_buffer_consume(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, - .close = wave_encoder_close, - .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 deleted file mode 100644 index 46c8d10c8..000000000 --- a/src/encoder_api.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This header is included by encoder plugins. - * - */ - -#ifndef MPD_ENCODER_API_H -#define MPD_ENCODER_API_H - -#include "encoder_plugin.h" -#include "audio_format.h" -#include "tag.h" -#include "conf.h" - -#endif diff --git a/src/encoder_list.c b/src/encoder_list.c deleted file mode 100644 index 2326c1099..000000000 --- a/src/encoder_list.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_list.h" -#include "encoder_plugin.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; - -const struct encoder_plugin *const 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 -}; - -const struct encoder_plugin * -encoder_plugin_get(const char *name) -{ - encoder_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} diff --git a/src/encoder_list.h b/src/encoder_list.h deleted file mode 100644 index fb1c9bf9c..000000000 --- a/src/encoder_list.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_LIST_H -#define MPD_ENCODER_LIST_H - -struct encoder_plugin; - -extern const struct encoder_plugin *const encoder_plugins[]; - -#define encoder_plugins_for_each(plugin) \ - for (const struct encoder_plugin *plugin, \ - *const*encoder_plugin_iterator = &encoder_plugins[0]; \ - (plugin = *encoder_plugin_iterator) != NULL; \ - ++encoder_plugin_iterator) - -/** - * Looks up an encoder plugin by its name. - * - * @param name the encoder name to look for - * @return the encoder plugin with the specified name, or NULL if none - * was found - */ -const struct encoder_plugin * -encoder_plugin_get(const char *name); - -#endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h deleted file mode 100644 index 3a42d79f4..000000000 --- a/src/encoder_plugin.h +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_PLUGIN_H -#define MPD_ENCODER_PLUGIN_H - -#include <glib.h> - -#include <assert.h> -#include <stdbool.h> -#include <stddef.h> - -struct encoder_plugin; -struct audio_format; -struct config_param; -struct tag; - -struct encoder { - const struct encoder_plugin *plugin; - -#ifndef NDEBUG - bool open, pre_tag, tag, end; -#endif -}; - -struct encoder_plugin { - const char *name; - - struct encoder *(*init)(const struct config_param *param, - GError **error); - - void (*finish)(struct encoder *encoder); - - bool (*open)(struct encoder *encoder, - struct audio_format *audio_format, - GError **error); - - void (*close)(struct encoder *encoder); - - bool (*end)(struct encoder *encoder, GError **error); - - bool (*flush)(struct encoder *encoder, GError **error); - - bool (*pre_tag)(struct encoder *encoder, GError **error); - - bool (*tag)(struct encoder *encoder, const struct tag *tag, - GError **error); - - bool (*write)(struct encoder *encoder, - const void *data, size_t length, - GError **error); - - size_t (*read)(struct encoder *encoder, void *dest, size_t length); - - const char *(*get_mime_type)(struct encoder *encoder); -}; - -/** - * Initializes an encoder object. This should be used by encoder - * plugins to initialize their base class. - */ -static inline void -encoder_struct_init(struct encoder *encoder, - const struct encoder_plugin *plugin) -{ - encoder->plugin = plugin; - -#ifndef NDEBUG - encoder->open = false; -#endif -} - -/** - * Creates a new encoder object. - * - * @param plugin the encoder plugin - * @param param optional configuration - * @param error location to store the error occurring, or NULL to ignore errors. - * @return an encoder object on success, NULL on failure - */ -static inline struct encoder * -encoder_init(const struct encoder_plugin *plugin, - const struct config_param *param, GError **error) -{ - return plugin->init(param, error); -} - -/** - * Frees an encoder object. - * - * @param encoder the encoder - */ -static inline void -encoder_finish(struct encoder *encoder) -{ - assert(!encoder->open); - - encoder->plugin->finish(encoder); -} - -/** - * Opens an encoder object. You must call this prior to using it. - * Before you free it, you must call encoder_close(). You may open - * and close (reuse) one encoder any number of times. - * - * After this function returns successfully and before the first - * encoder_write() call, you should invoke encoder_read() to obtain - * the file header. - * - * @param encoder the encoder - * @param audio_format the encoder's input audio format; the plugin - * may modify the struct to adapt it to its abilities - * @param error location to store the error occurring, or NULL to ignore errors. - * @return true on success - */ -static inline bool -encoder_open(struct encoder *encoder, struct audio_format *audio_format, - GError **error) -{ - assert(!encoder->open); - - bool success = encoder->plugin->open(encoder, audio_format, error); -#ifndef NDEBUG - encoder->open = success; - encoder->pre_tag = encoder->tag = encoder->end = false; -#endif - return success; -} - -/** - * Closes an encoder object. This disables the encoder, and readies - * it for reusal by calling encoder_open() again. - * - * @param encoder the encoder - */ -static inline void -encoder_close(struct encoder *encoder) -{ - assert(encoder->open); - - if (encoder->plugin->close != NULL) - encoder->plugin->close(encoder); - -#ifndef NDEBUG - encoder->open = false; -#endif -} - -/** - * Ends the stream: flushes the encoder object, generate an - * end-of-stream marker (if applicable), make everything which might - * currently be buffered available by encoder_read(). - * - * After this function has been called, the encoder may not be usable - * for more data, and only encoder_read() and encoder_close() can be - * called. - * - * @param encoder the encoder - * @param error location to store the error occuring, or NULL to ignore errors. - * @return true on success - */ -static inline bool -encoder_end(struct encoder *encoder, GError **error) -{ - assert(encoder->open); - assert(!encoder->end); - -#ifndef NDEBUG - encoder->end = true; -#endif - - /* this method is optional */ - return encoder->plugin->end != NULL - ? encoder->plugin->end(encoder, error) - : true; -} - -/** - * Flushes an encoder object, make everything which might currently be - * buffered available by encoder_read(). - * - * @param encoder the encoder - * @param error location to store the error occurring, or NULL to ignore errors. - * @return true on success - */ -static inline bool -encoder_flush(struct encoder *encoder, GError **error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(!encoder->tag); - assert(!encoder->end); - - /* this method is optional */ - return encoder->plugin->flush != NULL - ? encoder->plugin->flush(encoder, error) - : true; -} - -/** - * Prepare for sending a tag to the encoder. This is used by some - * encoders to flush the previous sub-stream, in preparation to begin - * a new one. - * - * @param encoder the encoder - * @param tag the tag object - * @param error location to store the error occuring, or NULL to ignore errors. - * @return true on success - */ -static inline bool -encoder_pre_tag(struct encoder *encoder, GError **error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(!encoder->tag); - assert(!encoder->end); - - /* this method is optional */ - bool success = encoder->plugin->pre_tag != NULL - ? encoder->plugin->pre_tag(encoder, error) - : true; - -#ifndef NDEBUG - encoder->pre_tag = success; -#endif - return success; -} - -/** - * Sends a tag to the encoder. - * - * Instructions: call encoder_pre_tag(); then obtain flushed data with - * encoder_read(); finally call encoder_tag(). - * - * @param encoder the encoder - * @param tag the tag object - * @param error location to store the error occurring, or NULL to ignore errors. - * @return true on success - */ -static inline bool -encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(encoder->tag); - assert(!encoder->end); - -#ifndef NDEBUG - encoder->tag = false; -#endif - - /* this method is optional */ - return encoder->plugin->tag != NULL - ? encoder->plugin->tag(encoder, tag, error) - : true; -} - -/** - * Writes raw PCM data to the encoder. - * - * @param encoder the encoder - * @param data the buffer containing PCM samples - * @param length the length of the buffer in bytes - * @param error location to store the error occurring, or NULL to ignore errors. - * @return true on success - */ -static inline bool -encoder_write(struct encoder *encoder, const void *data, size_t length, - GError **error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(!encoder->tag); - assert(!encoder->end); - - return encoder->plugin->write(encoder, data, length, error); -} - -/** - * Reads encoded data from the encoder. - * - * Call this repeatedly until no more data is returned. - * - * @param encoder the encoder - * @param dest the destination buffer to copy to - * @param length the maximum length of the destination buffer - * @return the number of bytes written to #dest - */ -static inline size_t -encoder_read(struct encoder *encoder, void *dest, size_t length) -{ - assert(encoder->open); - assert(!encoder->pre_tag || !encoder->tag); - -#ifndef NDEBUG - if (encoder->pre_tag) { - encoder->pre_tag = false; - encoder->tag = true; - } -#endif - - 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/BufferedSocket.cxx b/src/event/BufferedSocket.cxx new file mode 100644 index 000000000..f333a5987 --- /dev/null +++ b/src/event/BufferedSocket.cxx @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "BufferedSocket.hxx" +#include "system/SocketError.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +BufferedSocket::~BufferedSocket() +{ + if (input != nullptr) + fifo_buffer_free(input); +} + +BufferedSocket::ssize_t +BufferedSocket::DirectRead(void *data, size_t length) +{ + const auto nbytes = SocketMonitor::Read((char *)data, length); + if (gcc_likely(nbytes > 0)) + return nbytes; + + if (nbytes == 0) { + OnSocketClosed(); + return -1; + } + + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + return -1; +} + +bool +BufferedSocket::ReadToBuffer() +{ + assert(IsDefined()); + + if (input == nullptr) + input = fifo_buffer_new(8192); + + size_t length; + void *buffer = fifo_buffer_write(input, &length); + assert(buffer != nullptr); + + const auto nbytes = DirectRead(buffer, length); + if (nbytes > 0) + fifo_buffer_append(input, nbytes); + + return nbytes >= 0; +} + +bool +BufferedSocket::ResumeInput() +{ + assert(IsDefined()); + + if (input == nullptr) { + ScheduleRead(); + return true; + } + + while (true) { + size_t length; + const void *data = fifo_buffer_read(input, &length); + if (data == nullptr) { + ScheduleRead(); + return true; + } + + const auto result = OnSocketInput(data, length); + switch (result) { + case InputResult::MORE: + if (fifo_buffer_is_full(input)) { + // TODO + static constexpr Domain buffered_socket_domain("buffered_socket"); + Error error; + error.Set(buffered_socket_domain, + "Input buffer is full"); + OnSocketError(std::move(error)); + return false; + } + + ScheduleRead(); + return true; + + case InputResult::PAUSE: + CancelRead(); + return true; + + case InputResult::AGAIN: + continue; + + case InputResult::CLOSED: + return false; + } + } +} + +void +BufferedSocket::ConsumeInput(size_t nbytes) +{ + assert(IsDefined()); + + fifo_buffer_consume(input, nbytes); +} + +bool +BufferedSocket::OnSocketReady(unsigned flags) +{ + assert(IsDefined()); + + if (gcc_unlikely(flags & (ERROR|HANGUP))) { + OnSocketClosed(); + return false; + } + + if (flags & READ) { + assert(input == nullptr || !fifo_buffer_is_full(input)); + + if (!ReadToBuffer() || !ResumeInput()) + return false; + + if (input == nullptr || !fifo_buffer_is_full(input)) + ScheduleRead(); + } + + return true; +} diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx new file mode 100644 index 000000000..cc763c164 --- /dev/null +++ b/src/event/BufferedSocket.hxx @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_BUFFERED_SOCKET_HXX +#define MPD_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "SocketMonitor.hxx" +#include "gcc.h" + +struct fifo_buffer; +class Error; + +/** + * A #SocketMonitor specialization that adds an input buffer. + */ +class BufferedSocket : protected SocketMonitor { + fifo_buffer *input; + +public: + BufferedSocket(int _fd, EventLoop &_loop) + :SocketMonitor(_fd, _loop), input(nullptr) { + ScheduleRead(); + } + + ~BufferedSocket(); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + using SocketMonitor::Write; + +private: + ssize_t DirectRead(void *data, size_t length); + + /** + * Receive data from the socket to the input buffer. + * + * @return false if the socket has been closed + */ + bool ReadToBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool ResumeInput(); + + /** + * Mark a portion of the input buffer "consumed". Only + * allowed to be called from OnSocketInput(). This method + * does not invalidate the pointer passed to OnSocketInput() + * yet. + */ + void ConsumeInput(size_t nbytes); + + enum class InputResult { + /** + * The method was successful, and it is ready to + * read more data. + */ + MORE, + + /** + * The method does not want to get more data for now. + * It will call ResumeInput() when it's ready for + * more. + */ + PAUSE, + + /** + * The method wants to be called again immediately, if + * there's more data in the buffer. + */ + AGAIN, + + /** + * The method has closed the socket. + */ + CLOSED, + }; + + virtual InputResult OnSocketInput(const void *data, size_t length) = 0; + virtual void OnSocketError(Error &&error) = 0; + virtual void OnSocketClosed() = 0; + + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/Call.cxx b/src/event/Call.cxx new file mode 100644 index 000000000..e7d963ac3 --- /dev/null +++ b/src/event/Call.cxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Call.hxx" +#include "Loop.hxx" +#include "DeferredMonitor.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "gcc.h" + +#include <assert.h> + +class BlockingCallMonitor final +#ifndef USE_EPOLL + : DeferredMonitor +#endif +{ + const std::function<void()> f; + + Mutex mutex; + Cond cond; + + bool done; + +public: +#ifdef USE_EPOLL + BlockingCallMonitor(EventLoop &loop, std::function<void()> &&_f) + :f(std::move(_f)), done(false) { + loop.AddCall([this](){ + this->DoRun(); + }); + } +#else + BlockingCallMonitor(EventLoop &_loop, std::function<void()> &&_f) + :DeferredMonitor(_loop), f(std::move(_f)), done(false) {} +#endif + + void Run() { +#ifndef USE_EPOLL + assert(!done); + + Schedule(); +#endif + + mutex.lock(); + while (!done) + cond.wait(mutex); + mutex.unlock(); + } + +#ifndef USE_EPOLL +private: + virtual void RunDeferred() override { + DoRun(); + } + +#else +public: +#endif + void DoRun() { + assert(!done); + + f(); + + mutex.lock(); + done = true; + cond.signal(); + mutex.unlock(); + } +}; + +void +BlockingCall(EventLoop &loop, std::function<void()> &&f) +{ + if (loop.IsInside()) { + /* we're already inside the loop - we can simply call + the function */ + f(); + } else { + /* outside the EventLoop's thread - defer execution to + the EventLoop, wait for completion */ + BlockingCallMonitor m(loop, std::move(f)); + m.Run(); + } +} diff --git a/src/event/Call.hxx b/src/event/Call.hxx new file mode 100644 index 000000000..34d886ca5 --- /dev/null +++ b/src/event/Call.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EVENT_CALL_HXX +#define MPD_EVENT_CALL_HXX + +#include "check.h" + +#include <functional> + +class EventLoop; + +/** + * Call the given function in the context of the #EventLoop, and wait + * for it to finish. + */ +void +BlockingCall(EventLoop &loop, std::function<void()> &&f); + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx new file mode 100644 index 000000000..4ffffaa89 --- /dev/null +++ b/src/event/DeferredMonitor.cxx @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DeferredMonitor.hxx" +#include "Loop.hxx" + +void +DeferredMonitor::Cancel() +{ +#ifdef USE_EPOLL + pending = false; +#else + const auto id = source_id.exchange(0); + if (id != 0) + g_source_remove(id); +#endif +} + +void +DeferredMonitor::Schedule() +{ +#ifdef USE_EPOLL + if (!pending.exchange(true)) + fd.Write(); +#else + const unsigned id = loop.AddIdle(Callback, this); + const auto old_id = source_id.exchange(id); + if (old_id != 0) + g_source_remove(old_id); +#endif +} + +#ifdef USE_EPOLL + +bool +DeferredMonitor::OnSocketReady(unsigned) +{ + fd.Read(); + + if (pending.exchange(false)) + RunDeferred(); + + return true; +} + +#else + +void +DeferredMonitor::Run() +{ + const auto id = source_id.exchange(0); + if (id != 0) + RunDeferred(); +} + +gboolean +DeferredMonitor::Callback(gpointer data) +{ + DeferredMonitor &monitor = *(DeferredMonitor *)data; + monitor.Run(); + return false; +} + +#endif diff --git a/src/event/DeferredMonitor.hxx b/src/event/DeferredMonitor.hxx new file mode 100644 index 000000000..988dce2d8 --- /dev/null +++ b/src/event/DeferredMonitor.hxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOCKET_DEFERRED_MONITOR_HXX +#define MPD_SOCKET_DEFERRED_MONITOR_HXX + +#include "check.h" +#include "gcc.h" + +#ifdef USE_EPOLL +#include "SocketMonitor.hxx" +#include "WakeFD.hxx" +#else +#include <glib.h> +#endif + +#include <atomic> + +class EventLoop; + +/** + * Defer execution of an event into an #EventLoop. + */ +class DeferredMonitor +#ifdef USE_EPOLL + : private SocketMonitor +#endif +{ +#ifdef USE_EPOLL + std::atomic_bool pending; + WakeFD fd; +#else + EventLoop &loop; + + std::atomic<guint> source_id; +#endif + +public: +#ifdef USE_EPOLL + DeferredMonitor(EventLoop &_loop) + :SocketMonitor(_loop), pending(false) { + SocketMonitor::Open(fd.Get()); + SocketMonitor::Schedule(SocketMonitor::READ); + } +#else + DeferredMonitor(EventLoop &_loop) + :loop(_loop), source_id(0) {} +#endif + + ~DeferredMonitor() { +#ifdef USE_EPOLL + /* avoid closing the WakeFD twice */ + SocketMonitor::Steal(); +#else + Cancel(); +#endif + } + + EventLoop &GetEventLoop() { +#ifdef USE_EPOLL + return SocketMonitor::GetEventLoop(); +#else + return loop; +#endif + } + + void Schedule(); + void Cancel(); + +protected: + virtual void RunDeferred() = 0; + +private: +#ifdef USE_EPOLL + virtual bool OnSocketReady(unsigned flags) override final; +#else + void Run(); + static gboolean Callback(gpointer data); +#endif +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx new file mode 100644 index 000000000..3ffd9f416 --- /dev/null +++ b/src/event/FullyBufferedSocket.cxx @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FullyBufferedSocket.hxx" +#include "system/SocketError.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/socket.h> +#endif + +FullyBufferedSocket::ssize_t +FullyBufferedSocket::DirectWrite(const void *data, size_t length) +{ + const auto nbytes = SocketMonitor::Write((const char *)data, length); + if (gcc_unlikely(nbytes < 0)) { + const auto code = GetSocketError(); + if (IsSocketErrorAgain(code)) + return 0; + + Cancel(); + + if (IsSocketErrorClosed(code)) + OnSocketClosed(); + else + OnSocketError(NewSocketError(code)); + } + + return nbytes; +} + +bool +FullyBufferedSocket::WriteFromBuffer() +{ + assert(IsDefined()); + + size_t length; + const void *data = output.Read(&length); + if (data == nullptr) { + CancelWrite(); + return true; + } + + auto nbytes = DirectWrite(data, length); + if (gcc_unlikely(nbytes <= 0)) + return nbytes == 0; + + output.Consume(nbytes); + + if (output.IsEmpty()) + CancelWrite(); + + return true; +} + +bool +FullyBufferedSocket::Write(const void *data, size_t length) +{ + assert(IsDefined()); + +#if 0 + /* TODO: disabled because this would add overhead on some callers (the ones that often), but it may be useful */ + + if (output.IsEmpty()) { + /* try to write it directly first */ + const auto nbytes = DirectWrite(data, length); + if (gcc_likely(nbytes > 0)) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } else if (nbytes < 0) + return false; + } +#endif + + if (!output.Append(data, length)) { + // TODO + static constexpr Domain buffered_socket_domain("buffered_socket"); + Error error; + error.Set(buffered_socket_domain, "Output buffer is full"); + OnSocketError(std::move(error)); + return false; + } + + ScheduleWrite(); + return true; +} + +bool +FullyBufferedSocket::OnSocketReady(unsigned flags) +{ + const bool was_empty = output.IsEmpty(); + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (was_empty && !output.IsEmpty()) + /* just in case the OnSocketInput() method has added + data to the output buffer: try to send it now + instead of waiting for the next event loop + iteration */ + flags |= WRITE; + + if (flags & WRITE) { + assert(!output.IsEmpty()); + + if (!WriteFromBuffer()) + return false; + } + + return true; +} diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx new file mode 100644 index 000000000..c67c2c78d --- /dev/null +++ b/src/event/FullyBufferedSocket.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FULLY_BUFFERED_SOCKET_HXX +#define MPD_FULLY_BUFFERED_SOCKET_HXX + +#include "check.h" +#include "BufferedSocket.hxx" +#include "util/PeakBuffer.hxx" +#include "gcc.h" + +/** + * A #BufferedSocket specialization that adds an output buffer. + */ +class FullyBufferedSocket : protected BufferedSocket { + PeakBuffer output; + +public: + FullyBufferedSocket(int _fd, EventLoop &_loop, + size_t normal_size, size_t peak_size=0) + :BufferedSocket(_fd, _loop), + output(normal_size, peak_size) { + } + + using BufferedSocket::IsDefined; + using BufferedSocket::Close; + +private: + ssize_t DirectWrite(const void *data, size_t length); + + /** + * Send data from the output buffer to the socket. + * + * @return false if the socket has been closed + */ + bool WriteFromBuffer(); + +protected: + /** + * @return false if the socket has been closed + */ + bool Write(const void *data, size_t length); + + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/event/IdleMonitor.cxx b/src/event/IdleMonitor.cxx new file mode 100644 index 000000000..c99c66b26 --- /dev/null +++ b/src/event/IdleMonitor.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "IdleMonitor.hxx" +#include "Loop.hxx" + +void +IdleMonitor::Cancel() +{ + assert(loop.IsInside()); + + if (!IsActive()) + return; + +#ifdef USE_EPOLL + active = false; + loop.RemoveIdle(*this); +#else + g_source_remove(source_id); + source_id = 0; +#endif +} + +void +IdleMonitor::Schedule() +{ + assert(loop.IsInside()); + + if (IsActive()) + /* already scheduled */ + return; + +#ifdef USE_EPOLL + active = true; + loop.AddIdle(*this); +#else + source_id = loop.AddIdle(Callback, this); +#endif +} + +void +IdleMonitor::Run() +{ + assert(loop.IsInside()); + +#ifdef USE_EPOLL + assert(active); + active = false; +#else + assert(source_id != 0); + source_id = 0; +#endif + + OnIdle(); +} + +#ifndef USE_EPOLL + +gboolean +IdleMonitor::Callback(gpointer data) +{ + IdleMonitor &monitor = *(IdleMonitor *)data; + monitor.Run(); + return false; +} + +#endif diff --git a/src/event/IdleMonitor.hxx b/src/event/IdleMonitor.hxx new file mode 100644 index 000000000..c8e79eb1d --- /dev/null +++ b/src/event/IdleMonitor.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOCKET_IDLE_MONITOR_HXX +#define MPD_SOCKET_IDLE_MONITOR_HXX + +#include "check.h" + +#ifndef USE_EPOLL +#include <glib.h> +#endif + +class EventLoop; + +/** + * An event that runs when the EventLoop has become idle, before + * waiting for more events. This class is not thread-safe; all + * methods must be run from EventLoop's thread. + */ +class IdleMonitor { +#ifdef USE_EPOLL + friend class EventLoop; +#endif + + EventLoop &loop; + +#ifdef USE_EPOLL + bool active; +#else + guint source_id; +#endif + +public: +#ifdef USE_EPOLL + IdleMonitor(EventLoop &_loop) + :loop(_loop), active(false) {} +#else + IdleMonitor(EventLoop &_loop) + :loop(_loop), source_id(0) {} +#endif + + ~IdleMonitor() { + Cancel(); + } + + EventLoop &GetEventLoop() const { + return loop; + } + + bool IsActive() const { +#ifdef USE_EPOLL + return active; +#else + return source_id != 0; +#endif + } + + void Schedule(); + void Cancel(); + +protected: + virtual void OnIdle() = 0; + +private: + void Run(); +#ifndef USE_EPOLL + static gboolean Callback(gpointer data); +#endif +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx new file mode 100644 index 000000000..faf967f21 --- /dev/null +++ b/src/event/Loop.cxx @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Loop.hxx" +#include "system/clock.h" + +#ifdef USE_EPOLL + +#include "TimeoutMonitor.hxx" +#include "SocketMonitor.hxx" +#include "IdleMonitor.hxx" + +#include <algorithm> + +EventLoop::EventLoop(Default) + :SocketMonitor(*this), + now_ms(::monotonic_clock_ms()), + quit(false), + n_events(0), + thread(ThreadId::Null()) +{ + SocketMonitor::Open(wake_fd.Get()); + SocketMonitor::Schedule(SocketMonitor::READ); +} + +EventLoop::~EventLoop() +{ + assert(idle.empty()); + assert(timers.empty()); + + /* avoid closing the WakeFD twice */ + SocketMonitor::Steal(); +} + +void +EventLoop::Break() +{ + if (IsInside()) + quit = true; + else + AddCall([this]() { Break(); }); +} + +bool +EventLoop::RemoveFD(int _fd, SocketMonitor &m) +{ + for (unsigned i = 0, n = n_events; i < n; ++i) + if (events[i].data.ptr == &m) + events[i].events = 0; + + return epoll.Remove(_fd); +} + +void +EventLoop::AddIdle(IdleMonitor &i) +{ + assert(std::find(idle.begin(), idle.end(), &i) == idle.end()); + + idle.push_back(&i); +} + +void +EventLoop::RemoveIdle(IdleMonitor &i) +{ + auto it = std::find(idle.begin(), idle.end(), &i); + assert(it != idle.end()); + + idle.erase(it); +} + +void +EventLoop::AddTimer(TimeoutMonitor &t, unsigned ms) +{ + timers.insert(TimerRecord(t, now_ms + ms)); +} + +void +EventLoop::CancelTimer(TimeoutMonitor &t) +{ + for (auto i = timers.begin(), end = timers.end(); i != end; ++i) { + if (&i->timer == &t) { + timers.erase(i); + return; + } + } +} + +#endif + +void +EventLoop::Run() +{ + assert(thread.IsNull()); + thread = ThreadId::GetCurrent(); + +#ifdef USE_EPOLL + assert(!quit); + + do { + now_ms = ::monotonic_clock_ms(); + + /* invoke timers */ + + int timeout_ms; + while (true) { + auto i = timers.begin(); + if (i == timers.end()) { + timeout_ms = -1; + break; + } + + timeout_ms = i->due_ms - now_ms; + if (timeout_ms > 0) + break; + + TimeoutMonitor &m = i->timer; + timers.erase(i); + + m.Run(); + + if (quit) + return; + } + + /* invoke idle */ + + const bool idle_empty = idle.empty(); + while (!idle.empty()) { + IdleMonitor &m = *idle.front(); + idle.pop_front(); + m.Run(); + + if (quit) + return; + } + + if (!idle_empty) + /* re-evaluate timers because one of the + IdleMonitors may have added a new + timeout */ + continue; + + /* wait for new event */ + + const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms); + n_events = std::max(n, 0); + + now_ms = ::monotonic_clock_ms(); + + assert(!quit); + + /* invoke sockets */ + + for (int i = 0; i < n; ++i) { + const auto &e = events[i]; + + if (e.events != 0) { + SocketMonitor &m = *(SocketMonitor *)e.data.ptr; + m.Dispatch(e.events); + + if (quit) + break; + } + } + + n_events = 0; + } while (!quit); +#else + g_main_loop_run(loop); +#endif + + assert(thread.IsInside()); +} + +#ifdef USE_EPOLL + +void +EventLoop::AddCall(std::function<void()> &&f) +{ + mutex.lock(); + calls.push_back(f); + mutex.unlock(); + + wake_fd.Write(); +} + +bool +EventLoop::OnSocketReady(gcc_unused unsigned flags) +{ + assert(!quit); + + wake_fd.Read(); + + mutex.lock(); + + while (!calls.empty() && !quit) { + auto f = std::move(calls.front()); + calls.pop_front(); + + mutex.unlock(); + f(); + mutex.lock(); + } + + mutex.unlock(); + + return true; +} + +#else + +guint +EventLoop::AddIdle(GSourceFunc function, gpointer data) +{ + GSource *source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, GetContext()); + g_source_unref(source); + return id; +} + +GSource * +EventLoop::AddTimeout(guint interval_ms, + GSourceFunc function, gpointer data) +{ + GSource *source = g_timeout_source_new(interval_ms); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; +} + +GSource * +EventLoop::AddTimeoutSeconds(guint interval_s, + GSourceFunc function, gpointer data) +{ + GSource *source = g_timeout_source_new_seconds(interval_s); + g_source_set_callback(source, function, data, nullptr); + g_source_attach(source, GetContext()); + return source; +} + +#endif diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx new file mode 100644 index 000000000..ec90cdacf --- /dev/null +++ b/src/event/Loop.hxx @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EVENT_LOOP_HXX +#define MPD_EVENT_LOOP_HXX + +#include "check.h" +#include "thread/Id.hxx" +#include "gcc.h" + +#ifdef USE_EPOLL +#include "system/EPollFD.hxx" +#include "thread/Mutex.hxx" +#include "WakeFD.hxx" +#include "SocketMonitor.hxx" + +#include <functional> +#include <list> +#include <set> +#else +#include <glib.h> +#endif + +#ifdef USE_EPOLL +class TimeoutMonitor; +class IdleMonitor; +class SocketMonitor; +#endif + +#include <assert.h> + +class EventLoop final +#ifdef USE_EPOLL + : private SocketMonitor +#endif +{ +#ifdef USE_EPOLL + struct TimerRecord { + /** + * Projected monotonic_clock_ms() value when this + * timer is due. + */ + const unsigned due_ms; + + TimeoutMonitor &timer; + + constexpr TimerRecord(TimeoutMonitor &_timer, + unsigned _due_ms) + :due_ms(_due_ms), timer(_timer) {} + + bool operator<(const TimerRecord &other) const { + return due_ms < other.due_ms; + } + + bool IsDue(unsigned _now_ms) const { + return _now_ms >= due_ms; + } + }; + + EPollFD epoll; + + WakeFD wake_fd; + + std::multiset<TimerRecord> timers; + std::list<IdleMonitor *> idle; + + Mutex mutex; + std::list<std::function<void()>> calls; + + unsigned now_ms; + + bool quit; + + static constexpr unsigned MAX_EVENTS = 16; + unsigned n_events; + epoll_event events[MAX_EVENTS]; +#else + GMainContext *context; + GMainLoop *loop; +#endif + + /** + * A reference to the thread that is currently inside Run(). + */ + ThreadId thread; + +public: +#ifdef USE_EPOLL + struct Default {}; + + EventLoop(Default dummy=Default()); + ~EventLoop(); + + unsigned GetTimeMS() const { + return now_ms; + } + + void Break(); + + bool AddFD(int _fd, unsigned flags, SocketMonitor &m) { + return epoll.Add(_fd, flags, &m); + } + + bool ModifyFD(int _fd, unsigned flags, SocketMonitor &m) { + return epoll.Modify(_fd, flags, &m); + } + + bool RemoveFD(int fd, SocketMonitor &m); + + void AddIdle(IdleMonitor &i); + void RemoveIdle(IdleMonitor &i); + + void AddTimer(TimeoutMonitor &t, unsigned ms); + void CancelTimer(TimeoutMonitor &t); + + void AddCall(std::function<void()> &&f); + + void Run(); + +private: + virtual bool OnSocketReady(unsigned flags) override; + +public: +#else + EventLoop() + :context(g_main_context_new()), + loop(g_main_loop_new(context, false)), + thread(ThreadId::Null()) {} + + struct Default {}; + EventLoop(gcc_unused Default _dummy) + :context(g_main_context_ref(g_main_context_default())), + loop(g_main_loop_new(context, false)), + thread(ThreadId::Null()) {} + + ~EventLoop() { + g_main_loop_unref(loop); + g_main_context_unref(context); + } + + GMainContext *GetContext() { + return context; + } + + void WakeUp() { + g_main_context_wakeup(context); + } + + void Break() { + g_main_loop_quit(loop); + } + + void Run(); + + guint AddIdle(GSourceFunc function, gpointer data); + + GSource *AddTimeout(guint interval_ms, + GSourceFunc function, gpointer data); + + GSource *AddTimeoutSeconds(guint interval_s, + GSourceFunc function, gpointer data); +#endif + + /** + * Are we currently running inside this EventLoop's thread? + */ + gcc_pure + bool IsInside() const { + assert(!thread.IsNull()); + + return thread.IsInside(); + } +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx new file mode 100644 index 000000000..2ebad02e5 --- /dev/null +++ b/src/event/MultiSocketMonitor.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MultiSocketMonitor.hxx" +#include "Loop.hxx" +#include "system/fd_util.h" +#include "gcc.h" + +#include <assert.h> + +#ifdef USE_EPOLL + +MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) + :IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) { +} + +MultiSocketMonitor::~MultiSocketMonitor() +{ + // TODO +} + +void +MultiSocketMonitor::Prepare() +{ + int timeout_ms = PrepareSockets(); + if (timeout_ms >= 0) + TimeoutMonitor::Schedule(timeout_ms); + else + TimeoutMonitor::Cancel(); + +} + +void +MultiSocketMonitor::OnIdle() +{ + if (ready) { + ready = false; + DispatchSockets(); + + /* TODO: don't refresh always; require users to call + InvalidateSockets() */ + refresh = true; + } + + if (refresh) { + refresh = false; + Prepare(); + } +} + +#else + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs multi_socket_monitor_source_funcs = { + MultiSocketMonitor::Prepare, + MultiSocketMonitor::Check, + MultiSocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) + :loop(_loop), + source((Source *)g_source_new(&multi_socket_monitor_source_funcs, + sizeof(*source))), + absolute_timeout_us(-1) { + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); +} + +MultiSocketMonitor::~MultiSocketMonitor() +{ + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; +} + +bool +MultiSocketMonitor::Prepare(gint *timeout_r) +{ + int timeout_ms = *timeout_r = PrepareSockets(); + absolute_timeout_us = timeout_ms < 0 + ? uint64_t(-1) + : GetTime() + uint64_t(timeout_ms) * 1000; + + return false; +} + +bool +MultiSocketMonitor::Check() const +{ + if (GetTime() >= absolute_timeout_us) + return true; + + for (const auto &i : fds) + if (i.GetReturnedEvents() != 0) + return true; + + return false; +} + +/* + * GSource methods + * + */ + +gboolean +MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Prepare(timeout_r); +} + +gboolean +MultiSocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +MultiSocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + MultiSocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} + +#endif diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx new file mode 100644 index 000000000..fe74206a3 --- /dev/null +++ b/src/event/MultiSocketMonitor.hxx @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MULTI_SOCKET_MONITOR_HXX +#define MPD_MULTI_SOCKET_MONITOR_HXX + +#include "check.h" +#include "gcc.h" + +#ifdef USE_EPOLL +#include "IdleMonitor.hxx" +#include "TimeoutMonitor.hxx" +#include "SocketMonitor.hxx" +#else +#include "glib_compat.h" +#include <glib.h> + +#endif + +#include <forward_list> + +#include <assert.h> +#include <stdint.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +/** + * Monitor multiple sockets. + */ +class MultiSocketMonitor +#ifdef USE_EPOLL + : private IdleMonitor, private TimeoutMonitor +#endif +{ +#ifdef USE_EPOLL + class SingleFD final : public SocketMonitor { + MultiSocketMonitor &multi; + + unsigned revents; + + public: + SingleFD(MultiSocketMonitor &_multi, int _fd, unsigned events) + :SocketMonitor(_fd, _multi.GetEventLoop()), + multi(_multi), revents(0) { + Schedule(events); + } + + int GetFD() const { + return SocketMonitor::Get(); + } + + unsigned GetEvents() const { + return SocketMonitor::GetScheduledFlags(); + } + + void SetEvents(unsigned _events) { + revents &= _events; + SocketMonitor::Schedule(_events); + } + + unsigned GetReturnedEvents() const { + return revents; + } + + void ClearReturnedEvents() { + revents = 0; + } + + protected: + virtual bool OnSocketReady(unsigned flags) override { + revents = flags; + multi.SetReady(); + return true; + } + }; + + friend class SingleFD; + + bool ready, refresh; +#else + struct Source { + GSource base; + + MultiSocketMonitor *monitor; + }; + + struct SingleFD { + GPollFD pfd; + + constexpr SingleFD(gcc_unused MultiSocketMonitor &m, + int fd, unsigned events) + :pfd{fd, gushort(events), 0} {} + + constexpr int GetFD() const { + return pfd.fd; + } + + constexpr unsigned GetEvents() const { + return pfd.events; + } + + constexpr unsigned GetReturnedEvents() const { + return pfd.revents; + } + + void SetEvents(unsigned _events) { + pfd.events = _events; + } + }; + + EventLoop &loop; + Source *source; + uint64_t absolute_timeout_us; +#endif + + std::forward_list<SingleFD> fds; + +public: +#ifdef USE_EPOLL + static constexpr unsigned READ = SocketMonitor::READ; + static constexpr unsigned WRITE = SocketMonitor::WRITE; + static constexpr unsigned ERROR = SocketMonitor::ERROR; + static constexpr unsigned HANGUP = SocketMonitor::HANGUP; +#else + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; +#endif + + MultiSocketMonitor(EventLoop &_loop); + ~MultiSocketMonitor(); + +#ifdef USE_EPOLL + using IdleMonitor::GetEventLoop; +#else + EventLoop &GetEventLoop() { + return loop; + } +#endif + +public: +#ifndef USE_EPOLL + gcc_pure + uint64_t GetTime() const { + return g_source_get_time(&source->base); + } +#endif + + void InvalidateSockets() { +#ifdef USE_EPOLL + refresh = true; + IdleMonitor::Schedule(); +#else + /* no-op because GLib always calls the GSource's + "prepare" method before each poll() anyway */ +#endif + } + + void AddSocket(int fd, unsigned events) { + fds.emplace_front(*this, fd, events); +#ifndef USE_EPOLL + g_source_add_poll(&source->base, &fds.front().pfd); +#endif + } + + template<typename E> + void UpdateSocketList(E &&e) { + for (auto prev = fds.before_begin(), end = fds.end(), + i = std::next(prev); + i != end; i = std::next(prev)) { + assert(i->GetEvents() != 0); + + unsigned events = e(i->GetFD()); + if (events != 0) { + i->SetEvents(events); + prev = i; + } else { +#ifdef USE_EPOLL + i->Steal(); +#else + g_source_remove_poll(&source->base, &i->pfd); +#endif + fds.erase_after(prev); + } + } + } + +protected: + /** + * @return timeout [ms] or -1 for no timeout + */ + virtual int PrepareSockets() = 0; + virtual void DispatchSockets() = 0; + +#ifdef USE_EPOLL +private: + void SetReady() { + ready = true; + IdleMonitor::Schedule(); + } + + void Prepare(); + + virtual void OnTimeout() final { + SetReady(); + IdleMonitor::Schedule(); + } + + virtual void OnIdle() final; + +#else +public: + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + bool Prepare(gint *timeout_r); + bool Check() const; + + void Dispatch() { + DispatchSockets(); + } +#endif +}; + +#endif diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx new file mode 100644 index 000000000..c3dbbad3d --- /dev/null +++ b/src/event/ServerSocket.cxx @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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" + +#ifdef HAVE_STRUCT_UCRED +#define _GNU_SOURCE 1 +#endif + +#include "ServerSocket.hxx" +#include "system/SocketUtil.hxx" +#include "system/SocketError.hxx" +#include "event/SocketMonitor.hxx" +#include "system/Resolver.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.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 + +#define DEFAULT_PORT 6600 + +class OneServerSocket final : private SocketMonitor { + ServerSocket &parent; + + const unsigned serial; + + char *path; + + size_t address_length; + struct sockaddr *address; + +public: + OneServerSocket(EventLoop &_loop, ServerSocket &_parent, + unsigned _serial, + const struct sockaddr *_address, + size_t _address_length) + :SocketMonitor(_loop), + parent(_parent), serial(_serial), + path(nullptr), + address_length(_address_length), + address((sockaddr *)g_memdup(_address, _address_length)) + { + assert(_address != nullptr); + assert(_address_length > 0); + } + + OneServerSocket(const OneServerSocket &other) = delete; + OneServerSocket &operator=(const OneServerSocket &other) = delete; + + ~OneServerSocket() { + g_free(path); + g_free(address); + } + + unsigned GetSerial() const { + return serial; + } + + void SetPath(const char *_path) { + assert(path == nullptr); + + path = g_strdup(_path); + } + + bool Open(Error &error); + + using SocketMonitor::IsDefined; + using SocketMonitor::Close; + + char *ToString() const; + + void SetFD(int _fd) { + SocketMonitor::Open(_fd); + SocketMonitor::ScheduleRead(); + } + + void Accept(); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +static constexpr Domain server_socket_domain("server_socket"); + +/** + * Wraper for sockaddr_to_string() which never fails. + */ +char * +OneServerSocket::ToString() const +{ + char *p = sockaddr_to_string(address, address_length, IgnoreError()); + if (p == nullptr) + 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 +} + +inline void +OneServerSocket::Accept() +{ + struct sockaddr_storage peer_address; + size_t peer_address_length = sizeof(peer_address); + int peer_fd = + accept_cloexec_nonblock(Get(), (struct sockaddr*)&peer_address, + &peer_address_length); + if (peer_fd < 0) { + const SocketErrorMessage msg; + FormatError(server_socket_domain, + "accept() failed: %s", (const char *)msg); + return; + } + + if (socket_keepalive(peer_fd)) { + const SocketErrorMessage msg; + FormatError(server_socket_domain, + "Could not set TCP keepalive option: %s", + (const char *)msg); + } + + parent.OnAccept(peer_fd, + (const sockaddr &)peer_address, + peer_address_length, get_remote_uid(peer_fd)); +} + +bool +OneServerSocket::OnSocketReady(gcc_unused unsigned flags) +{ + Accept(); + return true; +} + +inline bool +OneServerSocket::Open(Error &error) +{ + assert(!IsDefined()); + + int _fd = socket_bind_listen(address->sa_family, + SOCK_STREAM, 0, + address, address_length, 5, + error); + if (_fd < 0) + return false; + + /* allow everybody to connect */ + + if (path != nullptr) + chmod(path, 0666); + + /* register in the GLib main loop */ + + SetFD(_fd); + + return true; +} + +ServerSocket::ServerSocket(EventLoop &_loop) + :loop(_loop), next_serial(1) {} + +/* this is just here to allow the OneServerSocket forward + declaration */ +ServerSocket::~ServerSocket() {} + +bool +ServerSocket::Open(Error &error) +{ + OneServerSocket *good = nullptr, *bad = nullptr; + Error last_error; + + for (auto &i : sockets) { + assert(i.GetSerial() > 0); + assert(good == nullptr || i.GetSerial() <= good->GetSerial()); + + if (bad != nullptr && i.GetSerial() != bad->GetSerial()) { + Close(); + error = std::move(last_error); + return false; + } + + Error error2; + if (!i.Open(error2)) { + if (good != nullptr && good->GetSerial() == i.GetSerial()) { + char *address_string = i.ToString(); + char *good_string = good->ToString(); + FormatWarning(server_socket_domain, + "bind to '%s' failed: %s " + "(continuing anyway, because " + "binding to '%s' succeeded)", + address_string, error2.GetMessage(), + good_string); + g_free(address_string); + g_free(good_string); + } else if (bad == nullptr) { + bad = &i; + + char *address_string = i.ToString(); + error2.FormatPrefix("Failed to bind to '%s': ", + address_string); + g_free(address_string); + + last_error = std::move(error2); + } + + continue; + } + + /* mark this socket as "good", and clear previous + errors */ + + good = &i; + + if (bad != nullptr) { + bad = nullptr; + last_error.Clear(); + } + } + + if (bad != nullptr) { + Close(); + error = std::move(last_error); + return false; + } + + return true; +} + +void +ServerSocket::Close() +{ + for (auto &i : sockets) + if (i.IsDefined()) + i.Close(); +} + +OneServerSocket & +ServerSocket::AddAddress(const sockaddr &address, size_t address_length) +{ + sockets.emplace_front(loop, *this, next_serial, + &address, address_length); + + return sockets.front(); +} + +bool +ServerSocket::AddFD(int fd, Error &error) +{ + assert(fd >= 0); + + struct sockaddr_storage address; + socklen_t address_length = sizeof(address); + if (getsockname(fd, (struct sockaddr *)&address, + &address_length) < 0) { + SetSocketError(error); + error.AddPrefix("Failed to get socket address: "); + return false; + } + + OneServerSocket &s = AddAddress((const sockaddr &)address, + address_length); + s.SetFD(fd); + + return true; +} + +#ifdef HAVE_TCP + +inline void +ServerSocket::AddPortIPv4(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; + + AddAddress((const sockaddr &)sin, sizeof(sin)); +} + +#ifdef HAVE_IPV6 +inline void +ServerSocket::AddPortIPv6(unsigned port) +{ + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(sin)); + sin.sin6_port = htons(port); + sin.sin6_family = AF_INET6; + + AddAddress((const sockaddr &)sin, sizeof(sin)); +} +#endif /* HAVE_IPV6 */ + +#endif /* HAVE_TCP */ + +bool +ServerSocket::AddPort(unsigned port, Error &error) +{ +#ifdef HAVE_TCP + if (port == 0 || port > 0xffff) { + error.Set(server_socket_domain, "Invalid TCP port"); + return false; + } + +#ifdef HAVE_IPV6 + AddPortIPv6(port); +#endif + AddPortIPv4(port); + + ++next_serial; + + return true; +#else /* HAVE_TCP */ + (void)port; + + error.Set(server_socket_domain, "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +ServerSocket::AddHost(const char *hostname, unsigned port, Error &error) +{ +#ifdef HAVE_TCP + struct addrinfo *ai = resolve_host_port(hostname, port, + AI_PASSIVE, SOCK_STREAM, + error); + if (ai == nullptr) + return false; + + for (const struct addrinfo *i = ai; i != nullptr; i = i->ai_next) + AddAddress(*i->ai_addr, i->ai_addrlen); + + freeaddrinfo(ai); + + ++next_serial; + + return true; +#else /* HAVE_TCP */ + (void)hostname; + (void)port; + + error.Set(server_socket_domain, "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +ServerSocket::AddPath(const char *path, Error &error) +{ +#ifdef HAVE_UN + struct sockaddr_un s_un; + + size_t path_length = strlen(path); + if (path_length >= sizeof(s_un.sun_path)) { + error.Set(server_socket_domain, + "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); + + OneServerSocket &s = AddAddress((const sockaddr &)s_un, sizeof(s_un)); + s.SetPath(path); + + return true; +#else /* !HAVE_UN */ + (void)path; + + error.Set(server_socket_domain, + "UNIX domain socket support is disabled"); + return false; +#endif /* !HAVE_UN */ +} + diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx new file mode 100644 index 000000000..9f0745708 --- /dev/null +++ b/src/event/ServerSocket.hxx @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_SERVER_SOCKET_HXX + +#include <forward_list> + +#include <stddef.h> + +struct sockaddr; +class EventLoop; +class Error; + +typedef void (*server_socket_callback_t)(int fd, + const struct sockaddr *address, + size_t address_length, int uid, + void *ctx); + +class OneServerSocket; + +class ServerSocket { + friend class OneServerSocket; + + EventLoop &loop; + + std::forward_list<OneServerSocket> sockets; + + unsigned next_serial; + +public: + ServerSocket(EventLoop &_loop); + ~ServerSocket(); + + EventLoop &GetEventLoop() { + return loop; + } + +private: + OneServerSocket &AddAddress(const sockaddr &address, size_t length); + + /** + * Add a listener on a port on all IPv4 interfaces. + * + * @param port the TCP port + */ + void AddPortIPv4(unsigned port); + + /** + * Add a listener on a port on all IPv6 interfaces. + * + * @param port the TCP port + */ + void AddPortIPv6(unsigned port); + +public: + /** + * Add a listener on a port on all interfaces. + * + * @param port the TCP port + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddPort(unsigned port, Error &error); + + /** + * 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 occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddHost(const char *hostname, unsigned port, Error &error); + + /** + * Add a listener on a Unix domain socket. + * + * @param path the absolute socket path + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return true on success + */ + bool AddPath(const char *path, Error &error); + + /** + * Add a socket descriptor that is accepting connections. After this + * has been called, don't call server_socket_open(), because the + * socket is already open. + */ + bool AddFD(int fd, Error &error); + + bool Open(Error &error); + void Close(); + +protected: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) = 0; +}; + +#endif diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx new file mode 100644 index 000000000..ee72dea72 --- /dev/null +++ b/src/event/SignalMonitor.cxx @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SignalMonitor.hxx" + +#ifndef WIN32 + +#include "WakeFD.hxx" +#include "SocketMonitor.hxx" +#include "util/Manual.hxx" +#include "system/FatalError.hxx" + +#ifdef USE_SIGNALFD +#include "system/SignalFD.hxx" +#else +#include "WakeFD.hxx" +#endif + +#ifndef USE_SIGNALFD +#include <atomic> +#endif + +#include <algorithm> + +class SignalMonitor final : private SocketMonitor { +#ifdef USE_SIGNALFD + SignalFD fd; +#else + WakeFD fd; +#endif + +public: + SignalMonitor(EventLoop &_loop) + :SocketMonitor(_loop) { +#ifndef USE_SIGNALFD + SocketMonitor::Open(fd.Get()); + SocketMonitor::ScheduleRead(); +#endif + } + + ~SignalMonitor() { + /* prevent the descriptor to be closed twice */ +#ifdef USE_SIGNALFD + if (SocketMonitor::IsDefined()) +#endif + SocketMonitor::Steal(); + } + +#ifdef USE_SIGNALFD + void Update(sigset_t &mask) { + const bool was_open = SocketMonitor::IsDefined(); + + fd.Create(mask); + + if (!was_open) { + SocketMonitor::Open(fd.Get()); + SocketMonitor::ScheduleRead(); + } + } +#else + void WakeUp() { + fd.Write(); + } +#endif + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +/* this should be enough - is it? */ +static constexpr unsigned MAX_SIGNAL = 64; + +static SignalHandler signal_handlers[MAX_SIGNAL]; + +#ifdef USE_SIGNALFD +static sigset_t signal_mask; +#else +static std::atomic_bool signal_pending[MAX_SIGNAL]; +#endif + +static Manual<SignalMonitor> monitor; + +#ifndef USE_SIGNALFD +static void +SignalCallback(int signo) +{ + assert(signal_handlers[signo] != nullptr); + + if (!signal_pending[signo].exchange(true)) + monitor->WakeUp(); +} +#endif + +void +SignalMonitorInit(EventLoop &loop) +{ +#ifdef USE_SIGNALFD + sigemptyset(&signal_mask); +#endif + + monitor.Construct(loop); +} + +#ifndef USE_SIGNALFD + +static void +x_sigaction(int signum, const struct sigaction &act) +{ + if (sigaction(signum, &act, nullptr) < 0) + FatalSystemError("sigaction() failed"); +} + +#endif + +void +SignalMonitorFinish() +{ +#ifdef USE_SIGNALFD + std::fill_n(signal_handlers, MAX_SIGNAL, nullptr); +#else + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + + for (unsigned i = 0; i < MAX_SIGNAL; ++i) { + if (signal_handlers[i] != nullptr) { + x_sigaction(i, sa); + signal_handlers[i] = nullptr; + } + } + + std::fill_n(signal_pending, MAX_SIGNAL, false); +#endif + + monitor.Destruct(); +} + +void +SignalMonitorRegister(int signo, SignalHandler handler) +{ + assert(signal_handlers[signo] == nullptr); +#ifndef USE_SIGNALFD + assert(!signal_pending[signo]); +#endif + + signal_handlers[signo] = handler; + +#ifdef USE_SIGNALFD + sigaddset(&signal_mask, signo); + + if (sigprocmask(SIG_BLOCK, &signal_mask, nullptr) < 0) + FatalSystemError("sigprocmask() failed"); + + monitor->Update(signal_mask); +#else + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SignalCallback; + x_sigaction(signo, sa); +#endif +} + +bool +SignalMonitor::OnSocketReady(unsigned) +{ +#ifdef USE_SIGNALFD + int signo; + while ((signo = fd.Read()) >= 0) { + assert(unsigned(signo) < MAX_SIGNAL); + assert(signal_handlers[signo] != nullptr); + + signal_handlers[signo](); + } +#else + fd.Read(); + + for (unsigned i = 0; i < MAX_SIGNAL; ++i) + if (signal_pending[i].exchange(false)) + signal_handlers[i](); +#endif + + return true; +} + +#endif diff --git a/src/event/SignalMonitor.hxx b/src/event/SignalMonitor.hxx new file mode 100644 index 000000000..94ab0aa30 --- /dev/null +++ b/src/event/SignalMonitor.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOCKET_SIGNAL_MONITOR_HXX +#define MPD_SOCKET_SIGNAL_MONITOR_HXX + +#include "check.h" + +class EventLoop; + +#ifndef WIN32 + +typedef void (*SignalHandler)(); + +/** + * Initialise the signal monitor subsystem. + */ +void +SignalMonitorInit(EventLoop &loop); + +/** + * Deinitialise the signal monitor subsystem. + */ +void +SignalMonitorFinish(); + +/** + * Register a handler for the specified signal. The handler will be + * invoked in a safe context. + */ +void +SignalMonitorRegister(int signo, SignalHandler handler); + +#else + +static inline void +SignalMonitorInit(EventLoop &) +{ +} + +static inline void +SignalMonitorFinish() +{ +} + +#endif + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx new file mode 100644 index 000000000..76dab9346 --- /dev/null +++ b/src/event/SocketMonitor.cxx @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SocketMonitor.hxx" +#include "Loop.hxx" +#include "system/fd_util.h" +#include "gcc.h" + +#include <assert.h> + +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#endif + +#ifdef USE_EPOLL + +void +SocketMonitor::Dispatch(unsigned flags) +{ + flags &= GetScheduledFlags(); + + if (flags != 0 && !OnSocketReady(flags) && IsDefined()) + Cancel(); +} + +#else + +/* + * GSource methods + * + */ + +gboolean +SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r) +{ + return false; +} + +gboolean +SocketMonitor::Check(GSource *_source) +{ + const Source &source = *(const Source *)_source; + const SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + return monitor.Check(); +} + +gboolean +SocketMonitor::Dispatch(GSource *_source, + gcc_unused GSourceFunc callback, + gcc_unused gpointer user_data) +{ + Source &source = *(Source *)_source; + SocketMonitor &monitor = *source.monitor; + assert(_source == &monitor.source->base); + + monitor.Dispatch(); + return true; +} + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs socket_monitor_source_funcs = { + SocketMonitor::Prepare, + SocketMonitor::Check, + SocketMonitor::Dispatch, + nullptr, + nullptr, + nullptr, +}; + +SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop) + :fd(-1), loop(_loop), + source(nullptr) { + assert(_fd >= 0); + + Open(_fd); +} + +#endif + +SocketMonitor::~SocketMonitor() +{ + if (IsDefined()) + Close(); +} + +void +SocketMonitor::Open(int _fd) +{ + assert(fd < 0); +#ifndef USE_EPOLL + assert(source == nullptr); +#endif + assert(_fd >= 0); + + fd = _fd; + +#ifndef USE_EPOLL + poll = {fd, 0, 0}; + + source = (Source *)g_source_new(&socket_monitor_source_funcs, + sizeof(*source)); + source->monitor = this; + + g_source_attach(&source->base, loop.GetContext()); + g_source_add_poll(&source->base, &poll); +#endif +} + +int +SocketMonitor::Steal() +{ + assert(IsDefined()); + + Cancel(); + + int result = fd; + fd = -1; + +#ifndef USE_EPOLL + g_source_destroy(&source->base); + g_source_unref(&source->base); + source = nullptr; +#endif + + return result; +} + +void +SocketMonitor::Close() +{ + close_socket(Steal()); +} + +void +SocketMonitor::Schedule(unsigned flags) +{ + assert(IsDefined()); + + if (flags == GetScheduledFlags()) + return; + +#ifdef USE_EPOLL + if (scheduled_flags == 0) + loop.AddFD(fd, flags, *this); + else if (flags == 0) + loop.RemoveFD(fd, *this); + else + loop.ModifyFD(fd, flags, *this); + + scheduled_flags = flags; +#else + poll.events = flags; + poll.revents &= flags; + + loop.WakeUp(); +#endif +} + +SocketMonitor::ssize_t +SocketMonitor::Read(void *data, size_t length) +{ + assert(IsDefined()); + + int flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + return recv(Get(), (char *)data, length, flags); +} + +SocketMonitor::ssize_t +SocketMonitor::Write(const void *data, size_t length) +{ + assert(IsDefined()); + + int flags = 0; +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + + return send(Get(), (const char *)data, length, flags); +} diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx new file mode 100644 index 000000000..d6efeac17 --- /dev/null +++ b/src/event/SocketMonitor.hxx @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOCKET_MONITOR_HXX +#define MPD_SOCKET_MONITOR_HXX + +#include "check.h" + +#ifdef USE_EPOLL +#include <sys/epoll.h> +#else +#include <glib.h> +#endif + +#include <type_traits> + +#include <assert.h> +#include <stddef.h> + +#ifdef WIN32 +/* ERRORis a WIN32 macro that poisons our namespace; this is a + kludge to allow us to use it anyway */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +class EventLoop; + +class SocketMonitor { +#ifdef USE_EPOLL +#else + struct Source { + GSource base; + + SocketMonitor *monitor; + }; +#endif + + int fd; + EventLoop &loop; + +#ifdef USE_EPOLL + /** + * A bit mask of events that is currently registered in the EventLoop. + */ + unsigned scheduled_flags; +#else + Source *source; + GPollFD poll; +#endif + +public: +#ifdef USE_EPOLL + static constexpr unsigned READ = EPOLLIN; + static constexpr unsigned WRITE = EPOLLOUT; + static constexpr unsigned ERROR = EPOLLERR; + static constexpr unsigned HANGUP = EPOLLHUP; +#else + static constexpr unsigned READ = G_IO_IN; + static constexpr unsigned WRITE = G_IO_OUT; + static constexpr unsigned ERROR = G_IO_ERR; + static constexpr unsigned HANGUP = G_IO_HUP; +#endif + + typedef std::make_signed<size_t>::type ssize_t; + +#ifdef USE_EPOLL + SocketMonitor(EventLoop &_loop) + :fd(-1), loop(_loop), scheduled_flags(0) {} + + SocketMonitor(int _fd, EventLoop &_loop) + :fd(_fd), loop(_loop), scheduled_flags(0) {} +#else + SocketMonitor(EventLoop &_loop) + :fd(-1), loop(_loop), source(nullptr) {} + + SocketMonitor(int _fd, EventLoop &_loop); +#endif + + ~SocketMonitor(); + + EventLoop &GetEventLoop() { + return loop; + } + + bool IsDefined() const { + return fd >= 0; + } + + int Get() const { + assert(IsDefined()); + + return fd; + } + + void Open(int _fd); + + /** + * "Steal" the socket descriptor. This abandons the socket + * and puts the responsibility for closing it to the caller. + */ + int Steal(); + + void Close(); + + unsigned GetScheduledFlags() const { + assert(IsDefined()); + +#ifdef USE_EPOLL + return scheduled_flags; +#else + return poll.events; +#endif + } + + void Schedule(unsigned flags); + + void Cancel() { + Schedule(0); + } + + void ScheduleRead() { + Schedule(GetScheduledFlags() | READ | HANGUP | ERROR); + } + + void ScheduleWrite() { + Schedule(GetScheduledFlags() | WRITE); + } + + void CancelRead() { + Schedule(GetScheduledFlags() & ~(READ|HANGUP|ERROR)); + } + + void CancelWrite() { + Schedule(GetScheduledFlags() & ~WRITE); + } + + ssize_t Read(void *data, size_t length); + ssize_t Write(const void *data, size_t length); + +protected: + /** + * @return false if the socket has been closed + */ + virtual bool OnSocketReady(unsigned flags) = 0; + +public: +#ifdef USE_EPOLL + void Dispatch(unsigned flags); +#else + /* GSource callbacks */ + static gboolean Prepare(GSource *source, gint *timeout_r); + static gboolean Check(GSource *source); + static gboolean Dispatch(GSource *source, GSourceFunc callback, + gpointer user_data); + +private: + bool Check() const { + assert(IsDefined()); + + return (poll.revents & poll.events) != 0; + } + + void Dispatch() { + assert(IsDefined()); + + OnSocketReady(poll.revents & poll.events); + } +#endif +}; + +#endif diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx new file mode 100644 index 000000000..cffad6b92 --- /dev/null +++ b/src/event/TimeoutMonitor.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TimeoutMonitor.hxx" +#include "Loop.hxx" + +void +TimeoutMonitor::Cancel() +{ + if (IsActive()) { +#ifdef USE_EPOLL + active = false; + loop.CancelTimer(*this); +#else + g_source_destroy(source); + g_source_unref(source); + source = nullptr; +#endif + } +} + +void +TimeoutMonitor::Schedule(unsigned ms) +{ + Cancel(); + +#ifdef USE_EPOLL + active = true; + loop.AddTimer(*this, ms); +#else + source = loop.AddTimeout(ms, Callback, this); +#endif +} + +void +TimeoutMonitor::ScheduleSeconds(unsigned s) +{ + Cancel(); + +#ifdef USE_EPOLL + Schedule(s * 1000u); +#else + source = loop.AddTimeoutSeconds(s, Callback, this); +#endif +} + +void +TimeoutMonitor::Run() +{ +#ifndef USE_EPOLL + Cancel(); +#endif + + OnTimeout(); +} + +#ifndef USE_EPOLL + +gboolean +TimeoutMonitor::Callback(gpointer data) +{ + TimeoutMonitor &monitor = *(TimeoutMonitor *)data; + monitor.Run(); + return false; +} + +#endif diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx new file mode 100644 index 000000000..98e4e5564 --- /dev/null +++ b/src/event/TimeoutMonitor.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOCKET_TIMEOUT_MONITOR_HXX +#define MPD_SOCKET_TIMEOUT_MONITOR_HXX + +#include "check.h" + +#ifndef USE_EPOLL +#include <glib.h> +#endif + +class EventLoop; + +class TimeoutMonitor { +#ifdef USE_EPOLL + friend class EventLoop; +#endif + + EventLoop &loop; + +#ifdef USE_EPOLL + bool active; +#else + GSource *source; +#endif + +public: +#ifdef USE_EPOLL + TimeoutMonitor(EventLoop &_loop) + :loop(_loop), active(false) { + } +#else + TimeoutMonitor(EventLoop &_loop) + :loop(_loop), source(nullptr) {} +#endif + + ~TimeoutMonitor() { + Cancel(); + } + + EventLoop &GetEventLoop() { + return loop; + } + + bool IsActive() const { +#ifdef USE_EPOLL + return active; +#else + return source != nullptr; +#endif + } + + void Schedule(unsigned ms); + void ScheduleSeconds(unsigned s); + void Cancel(); + +protected: + virtual void OnTimeout() = 0; + +private: + void Run(); + +#ifndef USE_EPOLL + static gboolean Callback(gpointer data); +#endif +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx new file mode 100644 index 000000000..ed1baafd8 --- /dev/null +++ b/src/event/WakeFD.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_WAKE_FD_HXX +#define MPD_WAKE_FD_HXX + +#include "check.h" + +#include <assert.h> + +#ifdef USE_EVENTFD +#include "system/EventFD.hxx" +#define WakeFD EventFD +#else +#include "system/EventPipe.hxx" +#define WakeFD EventPipe +#endif + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/event_pipe.c b/src/event_pipe.c deleted file mode 100644 index d5c3b9564..000000000 --- a/src/event_pipe.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "event_pipe.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <stdbool.h> -#include <assert.h> -#include <glib.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <unistd.h> - -#ifdef WIN32 -/* for _O_BINARY */ -#include <fcntl.h> -#endif - -#undef G_LOG_DOMAIN -#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]; -static event_pipe_callback_t event_pipe_callbacks[PIPE_EVENT_MAX]; - -/** - * Invoke the callback for a certain event. - */ -static void -event_pipe_invoke(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] != NULL); - - event_pipe_callbacks[event](); -} - -static gboolean -main_notify_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - char buffer[256]; - 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)); - g_mutex_unlock(event_pipe_mutex); - - for (unsigned i = 0; i < PIPE_EVENT_MAX; ++i) - if (events[i]) - /* invoke the event handler */ - event_pipe_invoke(i); - - return true; -} - -void event_pipe_init(void) -{ - GIOChannel *channel; - int ret; - - ret = pipe_cloexec_nonblock(event_pipe); - if (ret < 0) - 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); - - event_channel = channel; - - event_pipe_mutex = g_mutex_new(); -} - -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]); -} - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - assert(event_pipe_callbacks[event] == NULL); - - event_pipe_callbacks[event] = callback; -} - -void event_pipe_emit(enum pipe_event event) -{ - ssize_t w; - - assert((unsigned)event < PIPE_EVENT_MAX); - - g_mutex_lock(event_pipe_mutex); - if (pipe_events[event]) { - /* already set: don't write */ - g_mutex_unlock(event_pipe_mutex); - return; - } - - pipe_events[event] = true; - g_mutex_unlock(event_pipe_mutex); - - w = write(event_pipe[1], "", 1); - if (w < 0 && errno != EAGAIN && errno != EINTR) - MPD_ERROR("error writing to pipe: %s", strerror(errno)); -} - -void event_pipe_emit_fast(enum pipe_event event) -{ - assert((unsigned)event < PIPE_EVENT_MAX); - - pipe_events[event] = true; - - G_GNUC_UNUSED ssize_t nbytes = write(event_pipe[1], "", 1); -} diff --git a/src/event_pipe.h b/src/event_pipe.h deleted file mode 100644 index 3734bb86c..000000000 --- a/src/event_pipe.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef EVENT_PIPE_H -#define EVENT_PIPE_H - -#include <glib.h> - -enum pipe_event { - /** database update was finished */ - PIPE_EVENT_UPDATE, - - /** during database update, a song was deleted */ - PIPE_EVENT_DELETE, - - /** an idle event was emitted */ - PIPE_EVENT_IDLE, - - /** must call playlist_sync() */ - PIPE_EVENT_PLAYLIST, - - /** the current song's tag has changed */ - PIPE_EVENT_TAG, - - /** 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 -}; - -typedef void (*event_pipe_callback_t)(void); - -void event_pipe_init(void); - -void event_pipe_deinit(void); - -void -event_pipe_register(enum pipe_event event, event_pipe_callback_t callback); - -void event_pipe_emit(enum pipe_event event); - -/** - * Similar to event_pipe_emit(), but aimed for use in signal handlers: - * it doesn't lock the mutex, and doesn't log on error. That makes it - * potentially lossy, but for its intended use, that does not matter. - */ -void event_pipe_emit_fast(enum pipe_event event); - -#endif /* MAIN_NOTIFY_H */ diff --git a/src/exclude.c b/src/exclude.c deleted file mode 100644 index 438039d30..000000000 --- a/src/exclude.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * 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 deleted file mode 100644 index 5b1229e29..000000000 --- a/src/exclude.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * 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 deleted file mode 100644 index 882b4c7d5..000000000 --- a/src/fd_util.c +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2003-2011 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; -} - -int -socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]) -{ - int ret; - -#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) - ret = socketpair(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol, - sv); - if (ret >= 0 || errno != EINVAL) - return ret; -#endif - - ret = socketpair(domain, type, protocol, sv); - if (ret >= 0) { - fd_set_cloexec(sv[0], true); - fd_set_nonblock(sv[0]); - fd_set_cloexec(sv[1], true); - fd_set_nonblock(sv[1]); - } - - return ret; -} - -#endif - -int -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 - -int -close_socket(int fd) -{ -#ifdef WIN32 - return closesocket(fd); -#else - return close(fd); -#endif -} diff --git a/src/fd_util.h b/src/fd_util.h deleted file mode 100644 index dd4df7a13..000000000 --- a/src/fd_util.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "check.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]); - -/** - * Wrapper for socketpair(), which sets the flags CLOEXEC and NONBLOCK - * (atomically if supported by the OS). - */ -int -socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]); - -#endif - -/** - * 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 - -#ifdef HAVE_INOTIFY_INIT - -/** - * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically - * if supported by the OS). - */ -int -inotify_init_cloexec(void); - -#endif - -/** - * Portable wrapper for close(); use closesocket() on WIN32/WinSock. - */ -int -close_socket(int fd); - -#endif diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c deleted file mode 100644 index 915fb0579..000000000 --- a/src/fifo_buffer.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2003-2011 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" -#include "fifo_buffer.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -struct fifo_buffer { - size_t size, start, end; - unsigned char buffer[sizeof(size_t)]; -}; - -struct fifo_buffer * -fifo_buffer_new(size_t size) -{ - struct fifo_buffer *buffer; - - assert(size > 0); - - buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) - - sizeof(buffer->buffer) + size); - - buffer->size = size; - buffer->start = 0; - buffer->end = 0; - - return buffer; -} - -static void -fifo_buffer_move(struct fifo_buffer *buffer); - -struct fifo_buffer * -fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size) -{ - if (buffer == NULL) - return new_size > 0 - ? fifo_buffer_new(new_size) - : NULL; - - /* existing data must fit in new size */ - assert(new_size >= buffer->end - buffer->start); - - if (new_size == 0) { - fifo_buffer_free(buffer); - return NULL; - } - - /* compress the buffer when we're shrinking and the tail of - the buffer would exceed the new size */ - if (buffer->end > new_size) - fifo_buffer_move(buffer); - - /* existing data must fit in new size: second check */ - assert(buffer->end <= new_size); - - buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) + - new_size); - buffer->size = new_size; - return buffer; -} - -void -fifo_buffer_free(struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - g_free(buffer); -} - -size_t -fifo_buffer_capacity(const struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - return buffer->size; -} - -size_t -fifo_buffer_available(const struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - return buffer->end - buffer->start; -} - -void -fifo_buffer_clear(struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - buffer->start = 0; - buffer->end = 0; -} - -const void * -fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(length_r != NULL); - - if (buffer->start == buffer->end) - /* the buffer is empty */ - return NULL; - - *length_r = buffer->end - buffer->start; - return buffer->buffer + buffer->start; -} - -void -fifo_buffer_consume(struct fifo_buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(buffer->start + length <= buffer->end); - - buffer->start += length; -} - -/** - * Move data to the beginning of the buffer, to make room at the end. - */ -static void -fifo_buffer_move(struct fifo_buffer *buffer) -{ - if (buffer->start == 0) - return; - - if (buffer->end > buffer->start) - memmove(buffer->buffer, - buffer->buffer + buffer->start, - buffer->end - buffer->start); - - buffer->end -= buffer->start; - buffer->start = 0; -} - -void * -fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r) -{ - assert(buffer != NULL); - assert(buffer->end <= buffer->size); - assert(max_length_r != NULL); - - if (buffer->end == buffer->size) { - fifo_buffer_move(buffer); - if (buffer->end == buffer->size) - return NULL; - } else if (buffer->start > 0 && buffer->start == buffer->end) { - buffer->start = 0; - buffer->end = 0; - } - - *max_length_r = buffer->size - buffer->end; - return buffer->buffer + buffer->end; -} - -void -fifo_buffer_append(struct fifo_buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(buffer->end + length <= buffer->size); - - buffer->end += length; -} - -bool -fifo_buffer_is_empty(struct fifo_buffer *buffer) -{ - return buffer->start == buffer->end; -} - -bool -fifo_buffer_is_full(struct fifo_buffer *buffer) -{ - return buffer->start == 0 && buffer->end == buffer->size; -} diff --git a/src/fifo_buffer.h b/src/fifo_buffer.h deleted file mode 100644 index 3bdb23938..000000000 --- a/src/fifo_buffer.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 - * - * This is a general purpose FIFO buffer library. You may append data - * at the end, while another instance reads data from the beginning. - * It is optimized for zero-copy usage: you get pointers to the real - * buffer, where you may operate on. - * - * This library is not thread safe. - */ - -#ifndef MPD_FIFO_BUFFER_H -#define MPD_FIFO_BUFFER_H - -#include <stdbool.h> -#include <stddef.h> - -struct fifo_buffer; - -/** - * Creates a new #fifo_buffer object. Free this object with - * fifo_buffer_free(). - * - * @param size the size of the buffer in bytes - * @return the new #fifo_buffer object - */ -struct fifo_buffer * -fifo_buffer_new(size_t size); - -/** - * Change the capacity of the #fifo_buffer, while preserving existing - * data. - * - * @param buffer the old buffer, may be NULL - * @param new_size the requested new size of the #fifo_buffer; must - * not be smaller than the data which is stored in the old buffer - * @return the new buffer, may be NULL if the requested new size is 0 - */ -struct fifo_buffer * -fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size); - -/** - * Frees the resources consumed by this #fifo_buffer object. - */ -void -fifo_buffer_free(struct fifo_buffer *buffer); - -/** - * Return the capacity of the buffer, i.e. the size that was passed to - * fifo_buffer_new(). - */ -size_t -fifo_buffer_capacity(const struct fifo_buffer *buffer); - -/** - * Return the number of bytes currently stored in the buffer. - */ -size_t -fifo_buffer_available(const struct fifo_buffer *buffer); - -/** - * Clears all data currently in this #fifo_buffer object. This does - * not overwrite the actuall buffer; it just resets the internal - * pointers. - */ -void -fifo_buffer_clear(struct fifo_buffer *buffer); - -/** - * Reads from the beginning of the buffer. To remove consumed data - * from the buffer, call fifo_buffer_consume(). - * - * @param buffer the #fifo_buffer object - * @param length_r the maximum amount to read is returned here - * @return a pointer to the beginning of the buffer, or NULL if the - * buffer is empty - */ -const void * -fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r); - -/** - * Marks data at the beginning of the buffer as "consumed". - * - * @param buffer the #fifo_buffer object - * @param length the number of bytes which were consumed - */ -void -fifo_buffer_consume(struct fifo_buffer *buffer, size_t length); - -/** - * Prepares writing to the buffer. This returns a buffer which you - * can write to. To commit the write operation, call - * fifo_buffer_append(). - * - * @param buffer the #fifo_buffer object - * @param max_length_r the maximum amount to write is returned here - * @return a pointer to the end of the buffer, or NULL if the buffer - * is already full - */ -void * -fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r); - -/** - * Commits the write operation initiated by fifo_buffer_write(). - * - * @param buffer the #fifo_buffer object - * @param length the number of bytes which were written - */ -void -fifo_buffer_append(struct fifo_buffer *buffer, size_t length); - -/** - * Checks if the buffer is empty. - */ -bool -fifo_buffer_is_empty(struct fifo_buffer *buffer); - -/** - * Checks if the buffer is full. - */ -bool -fifo_buffer_is_full(struct fifo_buffer *buffer); - -#endif diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx new file mode 100644 index 000000000..4b5ebff4d --- /dev/null +++ b/src/filter/AutoConvertFilterPlugin.cxx @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AutoConvertFilterPlugin.hxx" +#include "ConvertFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "ConfigData.hxx" + +#include <assert.h> + +class AutoConvertFilter final : public Filter { + /** + * The underlying filter. + */ + Filter *filter; + + /** + * A convert_filter, just in case conversion is needed. nullptr + * if unused. + */ + Filter *convert; + +public: + AutoConvertFilter(Filter *_filter):filter(_filter) {} + ~AutoConvertFilter() { + delete filter; + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close() override; + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, + Error &error) override; +}; + +AudioFormat +AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error) +{ + assert(in_audio_format.IsValid()); + + /* open the "real" filter */ + + const AudioFormat child_audio_format = in_audio_format; + AudioFormat out_audio_format = filter->Open(in_audio_format, error); + if (!out_audio_format.IsDefined()) + return out_audio_format; + + /* need to convert? */ + + if (in_audio_format != child_audio_format) { + /* yes - create a convert_filter */ + + const config_param empty; + convert = filter_new(&convert_filter_plugin, empty, error); + if (convert == nullptr) { + filter->Close(); + return AudioFormat::Undefined(); + } + + AudioFormat audio_format2 = in_audio_format; + AudioFormat audio_format3 = + convert->Open(audio_format2, error); + if (!audio_format3.IsDefined()) { + delete convert; + filter->Close(); + return AudioFormat::Undefined(); + } + + assert(audio_format2 == in_audio_format); + + convert_filter_set(convert, child_audio_format); + } else + /* no */ + convert = nullptr; + + return out_audio_format; +} + +void +AutoConvertFilter::Close() +{ + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + filter->Close(); +} + +const void * +AutoConvertFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error) +{ + if (convert != nullptr) { + src = convert->FilterPCM(src, src_size, &src_size, error); + if (src == nullptr) + return nullptr; + } + + return filter->FilterPCM(src, src_size, dest_size_r, error); +} + +Filter * +autoconvert_filter_new(Filter *filter) +{ + return new AutoConvertFilter(filter); +} diff --git a/src/filter/AutoConvertFilterPlugin.hxx b/src/filter/AutoConvertFilterPlugin.hxx new file mode 100644 index 000000000..7db72a345 --- /dev/null +++ b/src/filter/AutoConvertFilterPlugin.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_AUTOCONVERT_FILTER_PLUGIN_HXX +#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX + +class 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. + */ +Filter * +autoconvert_filter_new(Filter *filter); + +#endif diff --git a/src/filter/ChainFilterPlugin.cxx b/src/filter/ChainFilterPlugin.cxx new file mode 100644 index 000000000..90e196d76 --- /dev/null +++ b/src/filter/ChainFilterPlugin.cxx @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ChainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <list> + +#include <assert.h> + +class ChainFilter final : public Filter { + struct Child { + const char *name; + Filter *filter; + + Child(const char *_name, Filter *_filter) + :name(_name), filter(_filter) {} + ~Child() { + delete filter; + } + + Child(const Child &) = delete; + Child &operator=(const Child &) = delete; + }; + + std::list<Child> children; + +public: + void Append(const char *name, Filter *filter) { + children.emplace_back(name, filter); + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error); + +private: + /** + * Close all filters in the chain until #until is reached. + * #until itself is not closed. + */ + void CloseUntil(const Filter *until); +}; + +static constexpr Domain chain_filter_domain("chain_filter"); + +static Filter * +chain_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new ChainFilter(); +} + +void +ChainFilter::CloseUntil(const Filter *until) +{ + for (auto &child : children) { + if (child.filter == until) + /* don't close this filter */ + return; + + /* close this filter */ + child.filter->Close(); + } + + /* this assertion fails if #until does not exist (anymore) */ + assert(false); + gcc_unreachable(); +} + +static AudioFormat +chain_open_child(const char *name, Filter *filter, + const AudioFormat &prev_audio_format, + Error &error) +{ + AudioFormat conv_audio_format = prev_audio_format; + const AudioFormat next_audio_format = + filter->Open(conv_audio_format, error); + if (!next_audio_format.IsDefined()) + return next_audio_format; + + if (conv_audio_format != prev_audio_format) { + struct audio_format_string s; + + filter->Close(); + + error.Format(chain_filter_domain, + "Audio format not supported by filter '%s': %s", + name, + audio_format_to_string(prev_audio_format, &s)); + return AudioFormat::Undefined(); + } + + return next_audio_format; +} + +AudioFormat +ChainFilter::Open(AudioFormat &in_audio_format, Error &error) +{ + AudioFormat audio_format = in_audio_format; + + for (auto &child : children) { + audio_format = chain_open_child(child.name, child.filter, + audio_format, error); + if (!audio_format.IsDefined()) { + /* rollback, close all children */ + CloseUntil(child.filter); + break; + } + } + + /* return the output format of the last filter */ + return audio_format; +} + +void +ChainFilter::Close() +{ + for (auto &child : children) + child.filter->Close(); +} + +const void * +ChainFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error) +{ + for (auto &child : children) { + /* feed the output of the previous filter as input + into the current one */ + src = child.filter->FilterPCM(src, src_size, &src_size, + error); + 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 = { + "chain", + chain_filter_init, +}; + +Filter * +filter_chain_new(void) +{ + return new ChainFilter(); +} + +void +filter_chain_append(Filter &_chain, const char *name, Filter *filter) +{ + ChainFilter &chain = (ChainFilter &)_chain; + + chain.Append(name, filter); +} diff --git a/src/filter/ChainFilterPlugin.hxx b/src/filter/ChainFilterPlugin.hxx new file mode 100644 index 000000000..884c7ca19 --- /dev/null +++ b/src/filter/ChainFilterPlugin.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_FILTER_CHAIN_HXX + +class Filter; + +/** + * Creates a new filter chain. + */ +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(Filter &chain, const char *name, Filter *filter); + +#endif diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx new file mode 100644 index 000000000..040f8426f --- /dev/null +++ b/src/filter/ConvertFilterPlugin.cxx @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConvertFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "pcm/PcmConvert.hxx" +#include "util/Manual.hxx" +#include "AudioFormat.hxx" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +class ConvertFilter final : public Filter { + /** + * The input audio format; PCM data is passed to the filter() + * method in this format. + */ + AudioFormat 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(). + */ + AudioFormat out_audio_format; + + Manual<PcmConvert> state; + +public: + void Set(const AudioFormat &_out_audio_format) { + assert(in_audio_format.IsValid()); + assert(out_audio_format.IsValid()); + assert(_out_audio_format.IsValid()); + + out_audio_format = _out_audio_format; + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close() override; + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, + Error &error) override; +}; + +static Filter * +convert_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new ConvertFilter(); +} + +AudioFormat +ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + assert(audio_format.IsValid()); + + in_audio_format = out_audio_format = audio_format; + state.Construct(); + + return in_audio_format; +} + +void +ConvertFilter::Close() +{ + state.Destruct(); + + poison_undefined(&in_audio_format, sizeof(in_audio_format)); + poison_undefined(&out_audio_format, sizeof(out_audio_format)); +} + +const void * +ConvertFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error) +{ + if (in_audio_format == out_audio_format) { + /* optimized special case: no-op */ + *dest_size_r = src_size; + return src; + } + + return state->Convert(in_audio_format, + src, src_size, + out_audio_format, dest_size_r, + error); +} + +const struct filter_plugin convert_filter_plugin = { + "convert", + convert_filter_init, +}; + +void +convert_filter_set(Filter *_filter, const AudioFormat out_audio_format) +{ + ConvertFilter *filter = (ConvertFilter *)_filter; + + filter->Set(out_audio_format); +} diff --git a/src/filter/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx new file mode 100644 index 000000000..c814aaf49 --- /dev/null +++ b/src/filter/ConvertFilterPlugin.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONVERT_FILTER_PLUGIN_HXX +#define MPD_CONVERT_FILTER_PLUGIN_HXX + +class Filter; +struct AudioFormat; + +/** + * 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(Filter *filter, AudioFormat out_audio_format); + +#endif diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx new file mode 100644 index 000000000..6c4f6b0e5 --- /dev/null +++ b/src/filter/NormalizeFilterPlugin.cxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "pcm/PcmBuffer.hxx" +#include "AudioFormat.hxx" +#include "AudioCompress/compress.h" + +#include <assert.h> +#include <string.h> + +class NormalizeFilter final : public Filter { + struct Compressor *compressor; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error); +}; + +static Filter * +normalize_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new NormalizeFilter(); +} + +AudioFormat +NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + audio_format.format = SampleFormat::S16; + + compressor = Compressor_new(0); + + return audio_format; +} + +void +NormalizeFilter::Close() +{ + buffer.Clear(); + Compressor_delete(compressor); +} + +const void * +NormalizeFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused Error &error) +{ + int16_t *dest = (int16_t *)buffer.Get(src_size); + memcpy(dest, src, src_size); + + Compressor_Process_int16(compressor, dest, src_size / 2); + + *dest_size_r = src_size; + return dest; +} + +const struct filter_plugin normalize_filter_plugin = { + "normalize", + normalize_filter_init, +}; diff --git a/src/filter/NullFilterPlugin.cxx b/src/filter/NullFilterPlugin.cxx new file mode 100644 index 000000000..ad585d4b6 --- /dev/null +++ b/src/filter/NullFilterPlugin.cxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "gcc.h" + +class NullFilter final : public Filter { +public: + virtual AudioFormat Open(AudioFormat &af, + gcc_unused Error &error) override { + return af; + } + + virtual void Close() override {} + + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, + gcc_unused Error &error) override { + *dest_size_r = src_size; + return src; + } +}; + +static Filter * +null_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new NullFilter(); +} + +const struct filter_plugin null_filter_plugin = { + "null", + null_filter_init, +}; diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx new file mode 100644 index 000000000..5ac9840b1 --- /dev/null +++ b/src/filter/ReplayGainFilterPlugin.cxx @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ReplayGainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "ReplayGainInfo.hxx" +#include "ReplayGainConfig.hxx" +#include "MixerControl.hxx" +#include "pcm/PcmVolume.hxx" +#include "pcm/PcmBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +static constexpr Domain replay_gain_domain("replay_gain"); + +class ReplayGainFilter final : public Filter { + /** + * If set, then this hardware mixer is used for applying + * replay gain, instead of the software volume library. + */ + 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; + + AudioFormat format; + + PcmBuffer buffer; + +public: + ReplayGainFilter() + :mixer(nullptr), mode(REPLAY_GAIN_OFF), + volume(PCM_VOLUME_1) { + replay_gain_info_init(&info); + } + + void SetMixer(Mixer *_mixer, unsigned _base) { + assert(_mixer == NULL || (_base > 0 && _base <= 100)); + + mixer = _mixer; + base = _base; + + Update(); + } + + void SetInfo(const struct replay_gain_info *_info) { + if (_info != NULL) { + info = *_info; + replay_gain_info_complete(&info); + } else + replay_gain_info_init(&info); + + Update(); + } + + void SetMode(enum replay_gain_mode _mode) { + if (_mode == mode) + /* no change */ + return; + + FormatDebug(replay_gain_domain, + "replay gain mode has changed %d->%d\n", + mode, _mode); + + mode = _mode; + Update(); + } + + /** + * Recalculates the new volume after a property was changed. + */ + void Update(); + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error); +}; + +void +ReplayGainFilter::Update() +{ + if (mode != REPLAY_GAIN_OFF) { + float scale = replay_gain_tuple_scale(&info.tuples[mode], + replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit); + FormatDebug(replay_gain_domain, + "scale=%f\n", (double)scale); + + volume = pcm_float_to_volume(scale); + } else + volume = PCM_VOLUME_1; + + if (mixer != NULL) { + /* update the hardware mixer volume */ + + unsigned _volume = (volume * base) / PCM_VOLUME_1; + if (_volume > 100) + _volume = 100; + + Error error; + if (!mixer_set_volume(mixer, _volume, error)) + LogError(error, "Failed to update hardware mixer"); + } +} + +static Filter * +replay_gain_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new ReplayGainFilter(); +} + +AudioFormat +ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error) +{ + format = af; + + return format; +} + +void +ReplayGainFilter::Close() +{ + buffer.Clear(); +} + +const void * +ReplayGainFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error) +{ + + *dest_size_r = src_size; + + if (volume == PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + void *dest = buffer.Get(src_size); + if (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); + + bool success = pcm_volume(dest, src_size, + format.format, + volume); + if (!success) { + error.Set(replay_gain_domain, "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin replay_gain_filter_plugin = { + "replay_gain", + replay_gain_filter_init, +}; + +void +replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer, + unsigned base) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMixer(mixer, base); +} + +void +replay_gain_filter_set_info(Filter *_filter, const replay_gain_info *info) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetInfo(info); +} + +void +replay_gain_filter_set_mode(Filter *_filter, enum replay_gain_mode mode) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMode(mode); +} diff --git a/src/filter/ReplayGainFilterPlugin.hxx b/src/filter/ReplayGainFilterPlugin.hxx new file mode 100644 index 000000000..89cbe6c96 --- /dev/null +++ b/src/filter/ReplayGainFilterPlugin.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FILTER_PLUGIN_HXX +#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX + +#include "ReplayGainInfo.hxx" + +class Filter; +class 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(Filter *_filter, 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(Filter *filter, const replay_gain_info *info); + +void +replay_gain_filter_set_mode(Filter *filter, enum replay_gain_mode mode); + +#endif diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx new file mode 100644 index 000000000..58c0b36d8 --- /dev/null +++ b/src/filter/RouteFilterPlugin.cxx @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigError.hxx" +#include "ConfigData.hxx" +#include "AudioFormat.hxx" +#include "CheckAudioFormat.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "pcm/PcmBuffer.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +class RouteFilter final : public Filter { + /** + * 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 + */ + AudioFormat input_format; + + /** + * The decided upon output format, once opened + */ + AudioFormat 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. + */ + PcmBuffer output_buffer; + +public: + RouteFilter():sources(nullptr) {} + ~RouteFilter() { + g_free(sources); + } + + /** + * 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 + */ + bool Configure(const config_param ¶m, Error &error); + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error); +}; + +bool +RouteFilter::Configure(const config_param ¶m, Error &error) { + + /* 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 *const routes = param.GetBlockValue("routes", "0>0, 1>1"); + + min_input_channels = 0; + 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) { + error.Format(config_domain, + "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 >= min_input_channels) + min_input_channels = source + 1; + if (dest >= min_output_channels) + min_output_channels = dest + 1; + + g_strfreev(sd); + } + + if (!audio_valid_channel_count(min_output_channels)) { + g_strfreev(tokens); + error.Format(config_domain, + "Invalid number of output channels requested: %d", + min_output_channels); + return false; + } + + // Allocate a map of "copy nothing to me" + sources = (signed char *) + g_malloc(min_output_channels * sizeof(signed char)); + + for (int i=0; i<min_output_channels; ++i) + 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) { + error.Format(config_domain, + "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); + + sources[dest] = source; + + g_strfreev(sd); + } + + g_strfreev(tokens); + + return true; +} + +static Filter * +route_filter_init(const config_param ¶m, Error &error) +{ + RouteFilter *filter = new RouteFilter(); + if (!filter->Configure(param, error)) { + delete filter; + return nullptr; + } + + return filter; +} + +AudioFormat +RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + // Copy the input format for later reference + input_format = audio_format; + input_frame_size = input_format.GetFrameSize(); + + // Decide on an output format which has enough channels, + // and is otherwise identical + output_format = audio_format; + output_format.channels = min_output_channels; + + // Precalculate this simple value, to speed up allocation later + output_frame_size = output_format.GetFrameSize(); + + return output_format; +} + +void +RouteFilter::Close() +{ + output_buffer.Clear(); +} + +const void * +RouteFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused Error &error) +{ + size_t number_of_frames = src_size / input_frame_size; + + const size_t bytes_per_frame_per_channel = input_format.GetSampleSize(); + + // A moving pointer that always refers to channel 0 in the input, at the currently handled frame + const uint8_t *base_source = (const uint8_t *)src; + + // Grow our reusable buffer, if needed, and set the moving pointer + *dest_size_r = number_of_frames * output_frame_size; + void *const result = output_buffer.Get(*dest_size_r); + + // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output + uint8_t *chan_destination = (uint8_t *)result; + + // 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<min_output_channels; ++c) { + if (sources[c] == -1 || + (unsigned)sources[c] >= 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 + + (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 += input_frame_size; + } + + // Here it is, ladies and gentlemen! Rerouted data! + return result; +} + +const struct filter_plugin route_filter_plugin = { + "route", + route_filter_init, +}; diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx new file mode 100644 index 000000000..cfc638f52 --- /dev/null +++ b/src/filter/VolumeFilterPlugin.cxx @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "VolumeFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "FilterRegistry.hxx" +#include "pcm/PcmVolume.hxx" +#include "pcm/PcmBuffer.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +class VolumeFilter final : public Filter { + /** + * The current volume, from 0 to #PCM_VOLUME_1. + */ + unsigned volume; + + AudioFormat format; + + PcmBuffer buffer; + +public: + VolumeFilter() + :volume(PCM_VOLUME_1) {} + + unsigned GetVolume() const { + assert(volume <= PCM_VOLUME_1); + + return volume; + } + + void SetVolume(unsigned _volume) { + assert(_volume <= PCM_VOLUME_1); + + volume = _volume; + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual const void *FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error); +}; + +static constexpr Domain volume_domain("pcm_volume"); + +static Filter * +volume_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new VolumeFilter(); +} + +AudioFormat +VolumeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + format = audio_format; + + return format; +} + +void +VolumeFilter::Close() +{ + buffer.Clear(); +} + +const void * +VolumeFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, Error &error) +{ + *dest_size_r = src_size; + + if (volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + void *dest = buffer.Get(src_size); + + if (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); + + bool success = pcm_volume(dest, src_size, + format.format, + volume); + if (!success) { + error.Set(volume_domain, "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin volume_filter_plugin = { + "volume", + volume_filter_init, +}; + +unsigned +volume_filter_get(const Filter *_filter) +{ + const VolumeFilter *filter = + (const VolumeFilter *)_filter; + + return filter->GetVolume(); +} + +void +volume_filter_set(Filter *_filter, unsigned volume) +{ + VolumeFilter *filter = (VolumeFilter *)_filter; + + filter->SetVolume(volume); +} + diff --git a/src/filter/VolumeFilterPlugin.hxx b/src/filter/VolumeFilterPlugin.hxx new file mode 100644 index 000000000..822b7e93a --- /dev/null +++ b/src/filter/VolumeFilterPlugin.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_VOLUME_FILTER_PLUGIN_HXX +#define MPD_VOLUME_FILTER_PLUGIN_HXX + +class Filter; + +unsigned +volume_filter_get(const Filter *filter); + +void +volume_filter_set(Filter *filter, unsigned volume); + +#endif diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c deleted file mode 100644 index 3826a0fb3..000000000 --- a/src/filter/autoconvert_filter_plugin.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h deleted file mode 100644 index def08ab7e..000000000 --- a/src/filter/autoconvert_filter_plugin.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef AUTOCONVERT_FILTER_PLUGIN_H -#define AUTOCONVERT_FILTER_PLUGIN_H - -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. - */ -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 deleted file mode 100644 index 2c785a36f..000000000 --- a/src/filter/chain_filter_plugin.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 1dba46667..000000000 --- a/src/filter/chain_filter_plugin.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * 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 deleted file mode 100644 index c55b69af2..000000000 --- a/src/filter/convert_filter_plugin.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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)); - - filter->out_audio_format = *out_audio_format; -} diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h deleted file mode 100644 index 156adf8e3..000000000 --- a/src/filter/convert_filter_plugin.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef 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 deleted file mode 100644 index 2151482e4..000000000 --- a/src/filter/normalize_filter_plugin.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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; - - 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 deleted file mode 100644 index e7c998827..000000000 --- a/src/filter/null_filter_plugin.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This 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 deleted file mode 100644 index 583a09f90..000000000 --- a/src/filter/replay_gain_filter_plugin.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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; - - 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.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 deleted file mode 100644 index 45b738e40..000000000 --- a/src/filter/replay_gain_filter_plugin.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef 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 deleted file mode 100644 index 3bf8677e5..000000000 --- a/src/filter/route_filter_plugin.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This 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 deleted file mode 100644 index 3260e8989..000000000 --- a/src/filter/volume_filter_plugin.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 <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; - - 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.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 deleted file mode 100644 index 5b16f7e57..000000000 --- a/src/filter/volume_filter_plugin.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef 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 deleted file mode 100644 index ab9bdb0c5..000000000 --- a/src/filter_config.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "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 deleted file mode 100644 index 920cbc07c..000000000 --- a/src/filter_config.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * 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 deleted file mode 100644 index 4e94599a2..000000000 --- a/src/filter_internal.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * 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 deleted file mode 100644 index 7173134b3..000000000 --- a/src/filter_plugin.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 58e34dfb2..000000000 --- a/src/filter_plugin.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the 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 occurring, 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 occurring, 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 occurring, 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 occurring, 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 deleted file mode 100644 index dc1889398..000000000 --- a/src/filter_registry.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index d3949c7c4..000000000 --- a/src/filter_registry.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This 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/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx new file mode 100644 index 000000000..4e4a56345 --- /dev/null +++ b/src/fs/DirectoryReader.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FS_DIRECTORY_READER_HXX +#define MPD_FS_DIRECTORY_READER_HXX + +#include "check.h" +#include "Path.hxx" + +#include <dirent.h> + +/** + * Reader for directory entries. + */ +class DirectoryReader { + DIR *const dirp; + dirent *ent; +public: + /** + * Creates new directory reader for the specified #dir. + */ + explicit DirectoryReader(const Path &dir) + : dirp(opendir(dir.c_str())), + ent(nullptr) { + } + + DirectoryReader(const DirectoryReader &other) = delete; + DirectoryReader &operator=(const DirectoryReader &other) = delete; + + /** + * Destroys this instance. + */ + ~DirectoryReader() { + if (!HasFailed()) + closedir(dirp); + } + + /** + * Checks if directory failed to open. + */ + bool HasFailed() const { + return dirp == nullptr; + } + + /** + * Checks if directory entry is available. + */ + bool HasEntry() const { + assert(!HasFailed()); + return ent != nullptr; + } + + /** + * Reads next directory entry. + */ + bool ReadEntry() { + assert(!HasFailed()); + ent = readdir(dirp); + return HasEntry(); + } + + /** + * Extracts directory entry that was previously read by #ReadEntry. + */ + Path GetEntry() const { + assert(HasEntry()); + return Path::FromFS(ent->d_name); + } +}; + +#endif diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx new file mode 100644 index 000000000..70ab01fbd --- /dev/null +++ b/src/fs/FileSystem.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FileSystem.hxx" + +#include <errno.h> + +Path ReadLink(const Path &path) +{ +#ifdef WIN32 + (void)path; + errno = EINVAL; + return Path::Null(); +#else + char buffer[MPD_PATH_MAX]; + ssize_t size = readlink(path.c_str(), buffer, MPD_PATH_MAX); + if (size < 0) + return Path::Null(); + if (size >= MPD_PATH_MAX) { + errno = ENOMEM; + return Path::Null(); + } + buffer[size] = '\0'; + return Path::FromFS(buffer); +#endif +} diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx new file mode 100644 index 000000000..d247f1a75 --- /dev/null +++ b/src/fs/FileSystem.hxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FS_FILESYSTEM_HXX +#define MPD_FS_FILESYSTEM_HXX + +#include "check.h" +#include "system/fd_util.h" + +#include "Path.hxx" + +#include <sys/stat.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> + +namespace FOpenMode { +/** + * Open mode for reading text files. + */ +constexpr Path::const_pointer ReadText = "r"; + +/** + * Open mode for reading binary files. + */ +constexpr Path::const_pointer ReadBinary = "rb"; + +/** + * Open mode for writing text files. + */ +constexpr Path::const_pointer WriteText = "w"; + +/** + * Open mode for writing binary files. + */ +constexpr Path::const_pointer WriteBinary = "wb"; + +/** + * Open mode for appending text files. + */ +constexpr Path::const_pointer AppendText = "a"; + +/** + * Open mode for appending binary files. + */ +constexpr Path::const_pointer AppendBinary = "ab"; +} + +/** + * Wrapper for fopen() that uses #Path names. + */ +static inline FILE *FOpen(const Path &file, Path::const_pointer mode) +{ + return fopen(file.c_str(), mode); +} + +/** + * Wrapper for open_cloexec() that uses #Path names. + */ +static inline int OpenFile(const Path &file, int flags, int mode) +{ + return open_cloexec(file.c_str(), flags, mode); +} + +/** + * Wrapper for rename() that uses #Path names. + */ +static inline bool RenameFile(const Path &oldpath, const Path &newpath) +{ + return rename(oldpath.c_str(), newpath.c_str()) == 0; +} + +/** + * Wrapper for stat() that uses #Path names. + */ +static inline bool StatFile(const Path &file, struct stat &buf, + bool follow_symlinks = true) +{ +#ifdef WIN32 + (void)follow_symlinks; + return stat(file.c_str(), &buf) == 0; +#else + int ret = follow_symlinks + ? stat(file.c_str(), &buf) + : lstat(file.c_str(), &buf); + return ret == 0; +#endif +} + +/** + * Wrapper for unlink() that uses #Path names. + */ +static inline bool RemoveFile(const Path &file) +{ + return unlink(file.c_str()) == 0; +} + +/** + * Wrapper for readlink() that uses #Path names. + */ +Path ReadLink(const Path &path); + +#ifndef WIN32 + +static inline bool +MakeFifo(const Path &path, mode_t mode) +{ + return mkfifo(path.c_str(), mode) == 0; +} + +#endif + +/** + * Wrapper for access() that uses #Path names. + */ +static inline bool CheckAccess(const Path &path, int mode) +{ +#ifdef WIN32 + (void)path; + (void)mode; + return true; +#else + return access(path.c_str(), mode) == 0; +#endif +} + +/** + * Checks if #Path exists and is a regular file. + */ +static inline bool FileExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks) && S_ISREG(buf.st_mode); +} + +/** + * Checks if #Path exists and is a directory. + */ +static inline bool DirectoryExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks) && S_ISDIR(buf.st_mode); +} + +/** + * Checks if #Path exists. + */ +static inline bool PathExists(const Path &path, + bool follow_symlinks = true) +{ + struct stat buf; + return StatFile(path, buf, follow_symlinks); +} + +#endif diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx new file mode 100644 index 000000000..09616c9f4 --- /dev/null +++ b/src/fs/Path.cxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "fs/Path.hxx" +#include "ConfigGlobal.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "gcc.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#ifdef G_OS_WIN32 +#include <windows.h> // for GetACP() +#include <stdio.h> // for sprintf() +#endif + +/** + * Maximal number of bytes required to represent path name in UTF-8 + * (including nul-terminator). + * This value is a rought estimate of upper bound. + * It's based on path name limit in bytes (MPD_PATH_MAX) + * and assumption that some weird encoding could represent some UTF-8 4 byte + * sequences with single byte. + */ +#define MPD_PATH_MAX_UTF8 ((MPD_PATH_MAX - 1) * 4 + 1) + +const Domain path_domain("path"); + +std::string fs_charset; + +inline Path::Path(Donate, pointer _value) + :value(_value) { + g_free(_value); +} + +/* no inlining, please */ +Path::~Path() {} + +Path +Path::Build(const_pointer a, const_pointer b) +{ + return Path(Donate(), g_build_filename(a, b, nullptr)); +} + +std::string Path::ToUTF8(const_pointer path_fs) +{ + if (path_fs == nullptr) + return std::string(); + + GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); + if (conv == reinterpret_cast<GIConv>(-1)) + return std::string(); + + // g_iconv() does not need nul-terminator, + // std::string could be created without it too. + char path_utf8[MPD_PATH_MAX_UTF8 - 1]; + char *in = const_cast<char *>(path_fs); + char *out = path_utf8; + size_t in_left = strlen(path_fs); + size_t out_left = sizeof(path_utf8); + + size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left); + + g_iconv_close(conv); + + if (ret == static_cast<size_t>(-1) || in_left > 0) + return std::string(); + + return std::string(path_utf8, sizeof(path_utf8) - out_left); +} + +Path Path::FromUTF8(const char *path_utf8) +{ + gchar *p; + + p = g_convert(path_utf8, -1, + fs_charset.c_str(), "utf-8", + NULL, NULL, NULL); + + return Path(Donate(), p); +} + +Path +Path::FromUTF8(const char *path_utf8, Error &error) +{ + Path path = FromUTF8(path_utf8); + if (path.IsNull()) + error.Format(path_domain, + "Failed to convert to file system charset: %s", + path_utf8); + + return path; +} + +Path +Path::GetDirectoryName() const +{ + return Path(Donate(), g_path_get_dirname(value.c_str())); +} + +gcc_pure +static bool +IsSupportedCharset(const char *charset) +{ + /* convert a space to check if the charset is valid */ + char *test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); + if (test == NULL) + return false; + + g_free(test); + return true; +} + +static void +SetFSCharset(const char *charset) +{ + assert(charset != NULL); + + if (!IsSupportedCharset(charset)) + FormatFatalError("invalid filesystem charset: %s", charset); + + fs_charset = charset; + + FormatDebug(path_domain, + "SetFSCharset: fs charset is: %s", fs_charset.c_str()); +} + +const std::string &Path::GetFSCharset() +{ + return fs_charset; +} + +void Path::GlobalInit() +{ + const char *charset = NULL; + + charset = config_get_string(CONF_FS_CHARSET, NULL); + if (charset == NULL) { +#ifndef G_OS_WIN32 + const gchar **encodings; + g_get_filename_charsets(&encodings); + + if (encodings[0] != NULL && *encodings[0] != '\0') + charset = encodings[0]; +#else /* G_OS_WIN32 */ + /* Glib claims that file system encoding is always utf-8 + * on native Win32 (i.e. not Cygwin). + * However this is true only if <gstdio.h> helpers are used. + * MPD uses regular <stdio.h> functions. + * Those functions use encoding determined by GetACP(). */ + static char win_charset[13]; + sprintf(win_charset, "cp%u", GetACP()); + charset = win_charset; +#endif + } + + if (charset) { + SetFSCharset(charset); + } else { + LogDebug(path_domain, + "setting filesystem charset to ISO-8859-1"); + SetFSCharset("ISO-8859-1"); + } +} diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx new file mode 100644 index 000000000..faeb669b7 --- /dev/null +++ b/src/fs/Path.hxx @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FS_PATH_HXX +#define MPD_FS_PATH_HXX + +#include "check.h" +#include "gcc.h" + +#include <algorithm> +#include <string> + +#include <assert.h> +#include <string.h> +#include <limits.h> + +#if !defined(MPD_PATH_MAX) +# if defined(WIN32) +# define MPD_PATH_MAX 260 +# elif defined(MAXPATHLEN) +# define MPD_PATH_MAX MAXPATHLEN +# elif defined(PATH_MAX) +# define MPD_PATH_MAX PATH_MAX +# else +# define MPD_PATH_MAX 256 +# endif +#endif + +class Error; + +extern const class Domain path_domain; + +/** + * A path name in the native file system character set. + */ +class Path { + typedef std::string string; + +public: + typedef string::value_type value_type; + typedef string::pointer pointer; + typedef string::const_pointer const_pointer; + +private: + string value; + + struct Donate {}; + + /** + * Donate the allocated pointer to a new #Path object. + */ + Path(Donate, pointer _value); + + Path(const_pointer _value):value(_value) {} + +public: + /** + * Copy a #Path object. + */ + Path(const Path &) = default; + + /** + * Move a #Path object. + */ + Path(Path &&other):value(std::move(other.value)) {} + + ~Path(); + + /** + * Return a "nulled" instance. Its IsNull() method will + * return true. Such an object must not be used. + * + * @see IsNull() + */ + gcc_const + static Path Null() { + return Path(""); + } + + /** + * Join two path components with the path separator. + */ + gcc_pure gcc_nonnull_all + static Path Build(const_pointer a, const_pointer b); + + gcc_pure gcc_nonnull_all + static Path Build(const_pointer a, const Path &b) { + return Build(a, b.c_str()); + } + + gcc_pure gcc_nonnull_all + static Path Build(const Path &a, const_pointer b) { + return Build(a.c_str(), b); + } + + gcc_pure + static Path Build(const Path &a, const Path &b) { + return Build(a.c_str(), b.c_str()); + } + + /** + * Convert a C string that is already in the filesystem + * character set to a #Path instance. + */ + gcc_pure + static Path FromFS(const_pointer fs) { + return Path(fs); + } + + /** + * Convert a UTF-8 C string to a #Path instance. + * Returns return a "nulled" instance on error. + */ + gcc_pure + static Path FromUTF8(const char *path_utf8); + + gcc_pure + static Path FromUTF8(const char *path_utf8, Error &error); + + /** + * Convert the path to UTF-8. + * Returns empty string on error or if #path_fs is null pointer. + */ + gcc_pure + static std::string ToUTF8(const_pointer path_fs); + + /** + * Performs global one-time initialization of this class. + */ + static void GlobalInit(); + + /** + * Gets file system character set name. + */ + static const std::string &GetFSCharset(); + + /** + * Copy a #Path object. + */ + Path &operator=(const Path &) = default; + + /** + * Move a #Path object. + */ + Path &operator=(Path &&other) { + value = std::move(other.value); + return *this; + } + + /** + * Check if this is a "nulled" instance. A "nulled" instance + * must not be used. + */ + bool IsNull() const { + return value.empty(); + } + + /** + * Clear this object's value, make it "nulled". + * + * @see IsNull() + */ + void SetNull() { + value.clear(); + } + + /** + * @return the length of this string in number of "value_type" + * elements (which may not be the number of characters). + */ + gcc_pure + size_t length() const { + return value.length(); + } + + /** + * Returns the value as a const C string. The returned + * pointer is invalidated whenever the value of life of this + * instance ends. + */ + gcc_pure + const_pointer c_str() const { + return value.c_str(); + } + + /** + * Convert the path to UTF-8. + * Returns empty string on error or if this instance is "nulled" + * (#IsNull returns true). + */ + std::string ToUTF8() const { + return ToUTF8(value.c_str()); + } + + /** + * Gets directory name of this path. + * Returns a "nulled" instance on error. + */ + gcc_pure + Path GetDirectoryName() const; +}; + +#endif @@ -32,6 +32,10 @@ */ #if GCC_CHECK_VERSION(3,0) +# define gcc_const __attribute__((const)) +# define gcc_pure __attribute__((pure)) +# define gcc_malloc __attribute__((malloc)) +# define gcc_noreturn __attribute__((noreturn)) # define gcc_must_check __attribute__ ((warn_unused_result)) # define gcc_packed __attribute__ ((packed)) /* these are very useful for type checking */ @@ -41,11 +45,22 @@ # define gcc_fprintf__ __attribute__ ((format(printf,4,5))) # define gcc_scanf __attribute__ ((format(scanf,1,2))) # define gcc_used __attribute__ ((used)) +# define gcc_unused __attribute__((unused)) +# define gcc_warn_unused_result __attribute__((warn_unused_result)) /* # define inline inline __attribute__ ((always_inline)) */ # define gcc_noinline __attribute__ ((noinline)) # define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) # define gcc_nonnull_all __attribute__((nonnull)) + +# define gcc_likely(x) __builtin_expect (!!(x), 1) +# define gcc_unlikely(x) __builtin_expect (!!(x), 0) + #else +# define gcc_unused +# define gcc_const +# define gcc_pure +# define gcc_malloc +# define gcc_noreturn # define gcc_must_check # define gcc_packed # define gcc_printf @@ -54,10 +69,44 @@ # define gcc_fprintf__ # define gcc_scanf # define gcc_used +# define gcc_unused +# define gcc_warn_unused_result /* # define inline */ # define gcc_noinline # define gcc_nonnull(...) # define gcc_nonnull_all + +# define gcc_likely(x) (x) +# define gcc_unlikely(x) (x) + +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define gcc_unreachable() __builtin_unreachable() +#else +#define gcc_unreachable() +#endif + +#ifdef __cplusplus + +#ifdef __GNUC__ +/* "__restrict__" is a GCC extension for C++ */ +#define restrict __restrict__ +#else +/* disable it on other compilers */ +#define restrict +#endif + +#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,6) +#error Your gcc version is too old. MPD requires gcc 4.6 or newer. +#endif + +/* support for C++11 "override" was added in gcc 4.7 */ +#if !defined(__clang__) && defined(__GNUC__) && !GCC_CHECK_VERSION(4,7) +#define override +#define final +#endif + #endif #endif /* MPD_GCC_H */ diff --git a/src/gerror.h b/src/gerror.h new file mode 100644 index 000000000..fe4c54da9 --- /dev/null +++ b/src/gerror.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_GERROR_H +#define MPD_GERROR_H + +typedef struct _GError GError; + +#endif diff --git a/src/glib_compat.h b/src/glib_compat.h index 97d1fdc0c..5277d317e 100644 --- a/src/glib_compat.h +++ b/src/glib_compat.h @@ -28,53 +28,14 @@ #include <glib.h> -#if !GLIB_CHECK_VERSION(2,18,0) - -static inline void -g_set_error_literal(GError **err, GQuark domain, gint code, - const gchar *message) -{ - g_set_error(err, domain, code, "%s", message); -} - -#endif - #if !GLIB_CHECK_VERSION(2,28,0) +#include "system/clock.h" + static inline gint64 g_source_get_time(GSource *source) { - GTimeVal tv; - g_source_get_current_time(source, &tv); - return tv.tv_sec * 1000000 + tv.tv_usec; -} - -#endif - -#if defined(G_OS_WIN32) && defined(g_file_test) - -/* Modern GLib on Win32 likes to use UTF-8 for file names. -It redefines g_file_test() to be g_file_test_utf8(). -This gives incorrect results for non-ASCII files. -Old g_file_test() is available for *binary compatibility*, -but symbol is hidden from linker, we copy-paste its definition here */ - -#undef g_file_test - -static inline gboolean -g_file_test(const gchar *filename, GFileTest test) -{ - gchar *utf8_filename = g_locale_to_utf8(filename, -1, NULL, NULL, NULL); - gboolean retval; - - if (utf8_filename == NULL) - return FALSE; - - retval = g_file_test_utf8(utf8_filename, test); - - g_free(utf8_filename); - - return retval; + return monotonic_clock_us(); } #endif diff --git a/src/glib_socket.h b/src/glib_socket.h deleted file mode 100644 index 46fab37dd..000000000 --- a/src/glib_socket.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_GLIB_SOCKET_H -#define MPD_GLIB_SOCKET_H - -#include <glib.h> - -/** - * Portable wrapper for g_io_channel_unix_new() or - * g_io_channel_win32_new_socket(). - */ -G_GNUC_MALLOC -static inline GIOChannel * -g_io_channel_new_socket(int fd) -{ -#ifdef G_OS_WIN32 - return g_io_channel_win32_new_socket(fd); -#else - return g_io_channel_unix_new(fd); -#endif -} - -#endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c deleted file mode 100644 index 32953e69f..000000000 --- a/src/icy_metadata.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "icy_metadata.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "icy_metadata" - -void -icy_deinit(struct icy_metadata *im) -{ - if (!icy_defined(im)) - return; - - if (im->data_rest == 0 && im->meta_size > 0) - g_free(im->meta_data); - - if (im->tag != NULL) - tag_free(im->tag); -} - -void -icy_reset(struct icy_metadata *im) -{ - if (!icy_defined(im)) - return; - - icy_deinit(im); - - im->data_rest = im->data_size; - im->meta_size = 0; -} - -size_t -icy_data(struct icy_metadata *im, size_t length) -{ - assert(length > 0); - - if (!icy_defined(im)) - return length; - - if (im->data_rest == 0) - return 0; - - if (length >= im->data_rest) { - length = im->data_rest; - im->data_rest = 0; - } else - im->data_rest -= length; - - return length; -} - -static void -icy_add_item(struct tag *tag, enum tag_type type, const char *value) -{ - size_t length = strlen(value); - - if (length >= 2 && value[0] == '\'' && value[length - 1] == '\'') { - /* strip the single quotes */ - ++value; - length -= 2; - } - - if (length > 0) - tag_add_item_n(tag, type, value, length); -} - -static void -icy_parse_tag_item(struct tag *tag, const char *item) -{ - gchar **p = g_strsplit(item, "=", 0); - - if (p[0] != NULL && p[1] != NULL) { - if (strcmp(p[0], "StreamTitle") == 0) - icy_add_item(tag, TAG_TITLE, p[1]); - else - g_debug("unknown icy-tag: '%s'", p[0]); - } - - g_strfreev(p); -} - -static struct tag * -icy_parse_tag(const char *p) -{ - struct tag *tag = tag_new(); - gchar **items = g_strsplit(p, ";", 0); - - for (unsigned i = 0; items[i] != NULL; ++i) - icy_parse_tag_item(tag, items[i]); - - g_strfreev(items); - - return tag; -} - -size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length) -{ - const unsigned char *p = data; - - assert(icy_defined(im)); - assert(im->data_rest == 0); - assert(length > 0); - - if (im->meta_size == 0) { - /* read meta_size from the first byte of a meta - block */ - im->meta_size = *p++ * 16; - if (im->meta_size == 0) { - /* special case: no metadata */ - im->data_rest = im->data_size; - return 1; - } - - /* 1 byte was consumed (must be re-added later for the - return value */ - --length; - - /* initialize metadata reader, allocate enough - memory (+1 for the null terminator) */ - im->meta_position = 0; - im->meta_data = g_malloc(im->meta_size + 1); - } - - assert(im->meta_position < im->meta_size); - - if (length > im->meta_size - im->meta_position) - length = im->meta_size - im->meta_position; - - memcpy(im->meta_data + im->meta_position, p, length); - im->meta_position += length; - - if (p != data) - /* re-add the first byte (which contained meta_size) */ - ++length; - - if (im->meta_position == im->meta_size) { - /* null-terminate the string */ - - im->meta_data[im->meta_size] = 0; - - /* parse */ - - if (im->tag != NULL) - tag_free(im->tag); - - im->tag = icy_parse_tag(im->meta_data); - g_free(im->meta_data); - - /* change back to normal data mode */ - - im->meta_size = 0; - im->data_rest = im->data_size; - } - - return length; -} diff --git a/src/icy_metadata.h b/src/icy_metadata.h deleted file mode 100644 index 9797122ca..000000000 --- a/src/icy_metadata.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ICY_METADATA_H -#define ICY_METADATA_H - -#include <stdbool.h> -#include <stddef.h> - -struct icy_metadata { - size_t data_size, data_rest; - - size_t meta_size, meta_position; - char *meta_data; - - struct tag *tag; -}; - -/** - * Initialize a disabled icy_metadata object. - */ -static inline void -icy_clear(struct icy_metadata *im) -{ - im->data_size = 0; -} - -/** - * Initialize an enabled icy_metadata object with the specified - * data_size (from the icy-metaint HTTP response header). - */ -static inline void -icy_start(struct icy_metadata *im, size_t data_size) -{ - im->data_size = im->data_rest = data_size; - im->meta_size = 0; - im->tag = NULL; -} - -/** - * Resets the icy_metadata. Call this after rewinding the stream. - */ -void -icy_reset(struct icy_metadata *im); - -void -icy_deinit(struct icy_metadata *im); - -/** - * Checks whether the icy_metadata object is enabled. - */ -static inline bool -icy_defined(const struct icy_metadata *im) -{ - return im->data_size > 0; -} - -/** - * Evaluates data. Returns the number of bytes of normal data which - * can be read by the caller, but not more than "length". If the - * return value is smaller than "length", the caller should invoke - * icy_meta(). - */ -size_t -icy_data(struct icy_metadata *im, size_t length); - -/** - * Reads metadata from the stream. Returns the number of bytes - * consumed. If the return value is smaller than "length", the caller - * should invoke icy_data(). - */ -size_t -icy_meta(struct icy_metadata *im, const void *data, size_t length); - -static inline struct tag * -icy_tag(struct icy_metadata *im) -{ - struct tag *tag = im->tag; - im->tag = NULL; - return tag; -} - -#endif diff --git a/src/icy_server.c b/src/icy_server.c deleted file mode 100644 index b6c89eaf6..000000000 --- a/src/icy_server.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "icy_server.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "icy_server" - -char* -icy_server_metadata_header(const char *name, - const char *genre, const char *url, - const char *content_type, int metaint) -{ - return g_strdup_printf("ICY 200 OK\r\n" - "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ - "icy-notice2:MPD - The music player daemon<BR>\r\n" - "icy-name: %s\r\n" /* TODO */ - "icy-genre: %s\r\n" /* TODO */ - "icy-url: %s\r\n" /* TODO */ - "icy-pub:1\r\n" - "icy-metaint:%d\r\n" - /* TODO "icy-br:%d\r\n" */ - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - name, - genre, - url, - metaint, - /* bitrate, */ - content_type); -} - -static char * -icy_server_metadata_string(const char *stream_title, const char* stream_url) -{ - gchar *icy_metadata; - guint meta_length; - - // The leading n is a placeholder for the length information - icy_metadata = g_strdup_printf("nStreamTitle='%s';" - "StreamUrl='%s';", - stream_title, - stream_url); - - g_return_val_if_fail(icy_metadata, NULL); - - meta_length = strlen(icy_metadata); - - meta_length--; // subtract placeholder - - meta_length = ((int)meta_length / 16) + 1; - - icy_metadata[0] = meta_length; - - if (meta_length > 255) { - g_free(icy_metadata); - return NULL; - } - - return icy_metadata; -} - -struct page* -icy_server_metadata_page(const struct tag *tag, ...) -{ - va_list args; - const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; - gint last_item, item; - guint position; - gchar *icy_string; - struct page *icy_metadata; - gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - - // "StreamTitle='';StreamUrl='';" - // = 4081 - 28 - stream_title[0] = '\0'; - - last_item = -1; - - va_start(args, tag); - while (1) { - enum tag_type type; - const gchar *tag_item; - - type = va_arg(args, enum tag_type); - - if (type == TAG_NUM_OF_ITEM_TYPES) - break; - - tag_item = tag_get_value(tag, type); - - if (tag_item) - tag_items[++last_item] = tag_item; - } - va_end(args); - - position = item = 0; - while (position < sizeof(stream_title) && item <= last_item) { - gint length = 0; - - length = g_strlcpy(stream_title + position, - tag_items[item++], - sizeof(stream_title) - position); - - position += length; - - if (item <= last_item) { - length = g_strlcpy(stream_title + position, - " - ", - sizeof(stream_title) - position); - - position += length; - } - } - - icy_string = icy_server_metadata_string(stream_title, ""); - - if (icy_string == NULL) - return NULL; - - icy_metadata = page_new_copy(icy_string, (icy_string[0] * 16) + 1); - - g_free(icy_string); - - return icy_metadata; -} diff --git a/src/icy_server.h b/src/icy_server.h deleted file mode 100644 index 04f21d2ad..000000000 --- a/src/icy_server.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ICY_SERVER_H -#define ICY_SERVER_H - -#include "page.h" -#include "tag.h" - -#include <stdarg.h> - -char* -icy_server_metadata_header(const char *name, - const char *genre, const char *url, - const char *content_type, int metaint); - -struct page* -icy_server_metadata_page(const struct tag *tag, ...); - -#endif diff --git a/src/idle.c b/src/idle.c deleted file mode 100644 index 2d174d78a..000000000 --- a/src/idle.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Support library for the "idle" command. - * - */ - -#include "config.h" -#include "idle.h" -#include "event_pipe.h" - -#include <assert.h> -#include <glib.h> - -static unsigned idle_flags; -static GMutex *idle_mutex = NULL; - -static const char *const idle_names[] = { - "database", - "stored_playlist", - "playlist", - "player", - "mixer", - "output", - "options", - "sticker", - "update", - "subscription", - "message", - NULL -}; - -void -idle_init(void) -{ - g_assert(idle_mutex == NULL); - idle_mutex = g_mutex_new(); -} - -void -idle_deinit(void) -{ - g_assert(idle_mutex != NULL); - g_mutex_free(idle_mutex); - idle_mutex = NULL; -} - -void -idle_add(unsigned flags) -{ - assert(flags != 0); - - g_mutex_lock(idle_mutex); - idle_flags |= flags; - g_mutex_unlock(idle_mutex); - - event_pipe_emit(PIPE_EVENT_IDLE); -} - -unsigned -idle_get(void) -{ - unsigned flags; - - g_mutex_lock(idle_mutex); - flags = idle_flags; - idle_flags = 0; - g_mutex_unlock(idle_mutex); - - return flags; -} - -const char*const* -idle_get_names(void) -{ - return idle_names; -} diff --git a/src/idle.h b/src/idle.h deleted file mode 100644 index 0156933c0..000000000 --- a/src/idle.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Support library for the "idle" command. - * - */ - -#ifndef MPD_IDLE_H -#define MPD_IDLE_H - -enum { - /** song database has been updated*/ - IDLE_DATABASE = 0x1, - - /** a stored playlist has been modified, created, deleted or - renamed */ - IDLE_STORED_PLAYLIST = 0x2, - - /** the current playlist has been modified */ - IDLE_PLAYLIST = 0x4, - - /** the player state has changed: play, stop, pause, seek, ... */ - IDLE_PLAYER = 0x8, - - /** the volume has been modified */ - IDLE_MIXER = 0x10, - - /** an audio output device has been enabled or disabled */ - IDLE_OUTPUT = 0x20, - - /** options have changed: crossfade, random, repeat, ... */ - IDLE_OPTIONS = 0x40, - - /** a sticker has been modified. */ - IDLE_STICKER = 0x80, - - /** a database update has started or finished. */ - IDLE_UPDATE = 0x100, - - /** a client has subscribed or unsubscribed to/from a channel */ - IDLE_SUBSCRIPTION = 0x200, - - /** a message on the subscribed channel was receivedd */ - IDLE_MESSAGE = 0x400, -}; - -/** - * Initialize the mutex - */ -void -idle_init(void); - -/** - * Destroy the mutex - */ -void -idle_deinit(void); - -/** - * Adds idle flag (with bitwise "or") and queues notifications to all - * clients. - */ -void -idle_add(unsigned flags); - -/** - * Atomically reads and resets the global idle flags value. - */ -unsigned -idle_get(void); - -/** - * Get idle names - */ -const char*const* -idle_get_names(void); - -#endif diff --git a/src/inotify_queue.c b/src/inotify_queue.c deleted file mode 100644 index d5e2228c3..000000000 --- a/src/inotify_queue.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index cfc28ebfe..000000000 --- a/src/inotify_queue.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index e415f5e72..000000000 --- a/src/inotify_source.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index f92e18e39..000000000 --- a/src/inotify_source.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index 3f4a8c0c4..000000000 --- a/src/inotify_update.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* 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 */ - const char *root = mapper_get_music_directory_fs(); - const char *path_fs; - char *allocated = NULL; - - if (uri_fs != NULL) - path_fs = allocated = - g_strconcat(root, "/", uri_fs, NULL); - else - path_fs = root; - - recursive_watch_subdirectories(directory, path_fs, - watch_directory_depth(directory)); - g_free(allocated); - } - - 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) -{ - GError *error = NULL; - - g_debug("initializing inotify"); - - const char *path = mapper_get_music_directory_fs(); - if (path == NULL) { - g_debug("no music directory configured"); - 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); - return; - } - - inotify_max_depth = max_depth; - - inotify_root.name = g_strdup(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; - 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 deleted file mode 100644 index ca75c0f45..000000000 --- a/src/inotify_update.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx new file mode 100644 index 000000000..08e1cabfe --- /dev/null +++ b/src/input/ArchiveInputPlugin.cxx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ArchiveInputPlugin.hxx" +#include "ArchiveLookup.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +static constexpr Domain archive_domain("archive"); + +/** + * select correct archive plugin to handle the input stream + * may allow stacking of archive plugins. for example for handling + * tar.gz a gzip handler opens file (through inputfile stream) + * then it opens a tar handler and sets gzip inputstream as + * parent_stream so tar plugin fetches file data from gzip + * plugin and gzip fetches file from disk + */ +static struct input_stream * +input_archive_open(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + const struct archive_plugin *arplug; + char *archive, *filename, *suffix, *pname; + struct input_stream *is; + + 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)) { + FormatDebug(archive_domain, + "not an archive, lookup %s failed", pname); + g_free(pname); + return NULL; + } + + //check which archive plugin to use (by ext) + arplug = archive_plugin_from_suffix(suffix); + if (!arplug) { + FormatWarning(archive_domain, + "can't handle archive %s", archive); + g_free(pname); + return NULL; + } + + auto file = archive_file_open(arplug, archive, error); + if (file == NULL) { + g_free(pname); + return NULL; + } + + //setup fileops + is = file->OpenStream(filename, mutex, cond, error); + g_free(pname); + file->Close(); + + return is; +} + +const struct input_plugin input_plugin_archive = { + "archive", + nullptr, + nullptr, + input_archive_open, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/ArchiveInputPlugin.hxx new file mode 100644 index 000000000..96fcd0dd1 --- /dev/null +++ b/src/input/ArchiveInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ARCHIVE_HXX +#define MPD_INPUT_ARCHIVE_HXX + +extern const struct input_plugin input_plugin_archive; + +#endif diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx new file mode 100644 index 000000000..65db12760 --- /dev/null +++ b/src/input/CdioParanoiaInputPlugin.cxx @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * CD-Audio handling (requires libcdio_paranoia) + */ + +#include "config.h" +#include "CdioParanoiaInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#include <cdio/paranoia.h> +#include <cdio/cd_types.h> + +struct CdioParanoiaInputStream { + struct input_stream base; + + cdrom_drive_t *drv; + CdIo_t *cdio; + cdrom_paranoia_t *para; + + lsn_t lsn_from, lsn_to; + int lsn_relofs; + + int trackno; + + char buffer[CDIO_CD_FRAMESIZE_RAW]; + int buffer_lsn; + + CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond, + int _trackno) + :base(input_plugin_cdio_paranoia, uri, mutex, cond), + drv(nullptr), cdio(nullptr), para(nullptr), + trackno(_trackno) + { + } + + ~CdioParanoiaInputStream() { + if (para != nullptr) + cdio_paranoia_free(para); + if (drv != nullptr) + cdio_cddap_close_no_free_cdio(drv); + if (cdio != nullptr) + cdio_destroy(cdio); + } +}; + +static constexpr Domain cdio_domain("cdio"); + +static void +input_cdio_close(struct input_stream *is) +{ + CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is; + + delete i; +} + +struct cdio_uri { + char device[64]; + int track; +}; + +static bool +parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error) +{ + if (!g_str_has_prefix(src, "cdda://")) + return false; + + src += 7; + + if (*src == 0) { + /* play the whole CD in the default drive */ + dest->device[0] = 0; + dest->track = -1; + return true; + } + + const char *slash = strrchr(src, '/'); + if (slash == nullptr) { + /* play the whole CD in the specified drive */ + g_strlcpy(dest->device, src, sizeof(dest->device)); + dest->track = -1; + return true; + } + + size_t device_length = slash - src; + if (device_length >= sizeof(dest->device)) + device_length = sizeof(dest->device) - 1; + + memcpy(dest->device, src, device_length); + dest->device[device_length] = 0; + + const char *track = slash + 1; + + char *endptr; + dest->track = strtoul(track, &endptr, 10); + if (*endptr != 0) { + error.Set(cdio_domain, "Malformed track number"); + return false; + } + + if (endptr == track) + /* play the whole CD */ + dest->track = -1; + + return true; +} + +static char * +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, + false); + if (devices == nullptr) + return nullptr; + + char *device = g_strdup(devices[0]); + cdio_free_device_list(devices); + + return device; +} + +static struct input_stream * +input_cdio_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + struct cdio_uri parsed_uri; + if (!parse_cdio_uri(&parsed_uri, uri, error)) + return nullptr; + + CdioParanoiaInputStream *i = + new CdioParanoiaInputStream(uri, mutex, cond, + parsed_uri.track); + + /* get list of CD's supporting CD-DA */ + char *device = parsed_uri.device[0] != 0 + ? g_strdup(parsed_uri.device) + : cdio_detect_device(); + if (device == nullptr) { + error.Set(cdio_domain, + "Unable find or access a CD-ROM drive with an audio CD in it."); + delete i; + return nullptr; + } + + /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ + i->cdio = cdio_open(device, DRIVER_UNKNOWN); + g_free(device); + + i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr); + + if ( !i->drv ) { + error.Set(cdio_domain, "Unable to identify audio CD disc."); + delete i; + return nullptr; + } + + cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + if ( 0 != cdio_cddap_open(i->drv) ) { + error.Set(cdio_domain, "Unable to open disc."); + delete i; + return nullptr; + } + + bool reverse_endian; + switch (data_bigendianp(i->drv)) { + case -1: + LogDebug(cdio_domain, "drive returns unknown audio data"); + reverse_endian = false; + break; + + case 0: + LogDebug(cdio_domain, "drive returns audio data Little Endian"); + reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; + break; + + case 1: + LogDebug(cdio_domain, "drive returns audio data Big Endian"); + reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; + break; + + default: + error.Format(cdio_domain, "Drive returns unknown data type %d", + data_bigendianp(i->drv)); + delete i; + return nullptr; + } + + i->lsn_relofs = 0; + + if (i->trackno >= 0) { + i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); + i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); + } else { + i->lsn_from = 0; + i->lsn_to = cdio_get_disc_last_lsn(i->cdio); + } + + i->para = cdio_paranoia_init(i->drv); + + /* Set reading mode for full paranoia, but allow skipping sectors. */ + paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + + /* seek to beginning of the track */ + cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); + + i->base.ready = true; + i->base.seekable = true; + i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; + + /* hack to make MPD select the "pcm" decoder plugin */ + i->base.mime = g_strdup(reverse_endian + ? "audio/x-mpd-cdda-pcm-reverse" + : "audio/x-mpd-cdda-pcm"); + + return &i->base; +} + +static bool +input_cdio_seek(struct input_stream *is, + goffset offset, int whence, Error &error) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + /* calculate absolute offset */ + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += cis->base.offset; + break; + case SEEK_END: + offset += cis->base.size; + break; + } + + if (offset < 0 || offset > cis->base.size) { + error.Format(cdio_domain, "Invalid offset to seek %ld (%ld)", + (long int)offset, (long int)cis->base.size); + return false; + } + + /* simple case */ + if (offset == cis->base.offset) + return true; + + /* calculate current LSN */ + cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + cis->base.offset = offset; + + cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); + + return true; +} + +static size_t +input_cdio_read(struct input_stream *is, void *ptr, size_t length, + Error &error) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + size_t nbytes = 0; + int diff; + size_t len, maxwrite; + int16_t *rbuf; + char *s_err, *s_mess; + char *wptr = (char *) ptr; + + while (length > 0) { + + + /* end of track ? */ + if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) + break; + + //current sector was changed ? + if (cis->lsn_relofs != cis->buffer_lsn) { + rbuf = cdio_paranoia_read(cis->para, nullptr); + + s_err = cdda_errors(cis->drv); + if (s_err) { + FormatError(cdio_domain, + "paranoia_read: %s", s_err); + free(s_err); + } + s_mess = cdda_messages(cis->drv); + if (s_mess) { + free(s_mess); + } + if (!rbuf) { + error.Set(cdio_domain, + "paranoia read error. Stopping."); + return 0; + } + //store current buffer + memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + cis->buffer_lsn = cis->lsn_relofs; + } else { + //use cached sector + rbuf = (int16_t*) cis->buffer; + } + + //correct offset + diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + + assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + + maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + len = (length < maxwrite? length : maxwrite); + + //skip diff bytes from this lsn + memcpy(wptr, ((char*)rbuf) + diff, len); + //update pointer + wptr += len; + nbytes += len; + + //update offset + cis->base.offset += len; + cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; + //update length + length -= len; + } + + return nbytes; +} + +static bool +input_cdio_eof(struct input_stream *is) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); +} + +const struct input_plugin input_plugin_cdio_paranoia = { + "cdio_paranoia", + nullptr, + nullptr, + input_cdio_open, + input_cdio_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_cdio_read, + input_cdio_eof, + input_cdio_seek, +}; diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/CdioParanoiaInputPlugin.hxx new file mode 100644 index 000000000..80d98b4bf --- /dev/null +++ b/src/input/CdioParanoiaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libcdio_paranoia library. + */ +extern const struct input_plugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx new file mode 100644 index 000000000..b707da2f0 --- /dev/null +++ b/src/input/CurlInputPlugin.cxx @@ -0,0 +1,1119 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CurlInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigData.hxx" +#include "tag/Tag.hxx" +#include "IcyMetaDataParser.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/Call.hxx" +#include "IOThread.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +#if defined(WIN32) + #include <winsock2.h> +#else + #include <sys/select.h> +#endif + +#include <string.h> +#include <errno.h> + +#include <list> +#include <forward_list> + +#include <curl/curl.h> +#include <glib.h> + +#if LIBCURL_VERSION_NUM < 0x071200 +#error libcurl is too old +#endif + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t CURL_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t CURL_RESUME_AT = 384 * 1024; + +/** + * Buffers created by input_curl_writefunction(). + */ +class CurlInputBuffer { + /** size of the payload */ + size_t size; + + /** how much has been consumed yet? */ + size_t consumed; + + /** the payload */ + uint8_t *data; + +public: + CurlInputBuffer(const void *_data, size_t _size) + :size(_size), consumed(0), data(new uint8_t[size]) { + memcpy(data, _data, size); + } + + ~CurlInputBuffer() { + delete[] data; + } + + CurlInputBuffer(const CurlInputBuffer &) = delete; + CurlInputBuffer &operator=(const CurlInputBuffer &) = delete; + + const void *Begin() const { + return data + consumed; + } + + size_t TotalSize() const { + return size; + } + + size_t Available() const { + return size - consumed; + } + + /** + * Mark a part of the buffer as consumed. + * + * @return false if the buffer is now empty + */ + bool Consume(size_t length) { + assert(consumed < size); + + consumed += length; + if (consumed < size) + return true; + + assert(consumed == size); + return false; + } + + bool Read(void *dest, size_t length) { + assert(consumed + length <= size); + + memcpy(dest, data + consumed, length); + return Consume(length); + } +}; + +struct input_curl { + struct input_stream base; + + /* some buffers which were passed to libcurl, which we have + too free */ + char *range; + struct curl_slist *request_headers; + + /** the curl handles */ + CURL *easy; + + /** list of buffers, where input_curl_writefunction() appends + to, and input_curl_read() reads from them */ + std::list<CurlInputBuffer> buffers; + + /** + * Is the connection currently paused? That happens when the + * buffer was getting too large. It will be unpaused when the + * buffer is below the threshold again. + */ + bool paused; + + /** error message provided by libcurl */ + char error[CURL_ERROR_SIZE]; + + /** parser for icy-metadata */ + IcyMetaDataParser icy; + + /** the stream name from the icy-name response header */ + char *meta_name; + + /** the tag object ready to be requested via + input_stream::ReadTag() */ + Tag *tag; + + Error postponed_error; + + input_curl(const char *url, Mutex &mutex, Cond &cond) + :base(input_plugin_curl, url, mutex, cond), + range(nullptr), request_headers(nullptr), + paused(false), + meta_name(nullptr), + tag(nullptr) {} + + ~input_curl(); + + input_curl(const input_curl &) = delete; + input_curl &operator=(const input_curl &) = delete; +}; + +/** + * This class monitors all CURL file descriptors. + */ +class CurlSockets final : private MultiSocketMonitor { +public: + CurlSockets(EventLoop &_loop) + :MultiSocketMonitor(_loop) {} + + using MultiSocketMonitor::InvalidateSockets; + +private: + void UpdateSockets(); + + virtual int PrepareSockets() override; + virtual void DispatchSockets() override; +}; + +/** libcurl should accept "ICY 200 OK" */ +static struct curl_slist *http_200_aliases; + +/** HTTP proxy settings */ +static const char *proxy, *proxy_user, *proxy_password; +static unsigned proxy_port; + +static struct { + CURLM *multi; + + /** + * A linked list of all active HTTP requests. An active + * request is one that doesn't have the "eof" flag set. + */ + std::forward_list<input_curl *> requests; + + CurlSockets *sockets; +} curl; + +static constexpr Domain http_domain("http"); +static constexpr Domain curl_domain("curl"); +static constexpr Domain curlm_domain("curlm"); + +/** + * Find a request by its CURL "easy" handle. + * + * Runs in the I/O thread. No lock needed. + */ +static struct input_curl * +input_curl_find_request(CURL *easy) +{ + assert(io_thread_inside()); + + for (auto c : curl.requests) + if (c->easy == easy) + return c; + + return NULL; +} + +static void +input_curl_resume(struct input_curl *c) +{ + assert(io_thread_inside()); + + if (c->paused) { + c->paused = false; + curl_easy_pause(c->easy, CURLPAUSE_CONT); + curl.sockets->InvalidateSockets(); + } +} + +/** + * Calculates the GLib event bit mask for one file descriptor, + * obtained from three #fd_set objects filled by curl_multi_fdset(). + */ +static unsigned +input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + gushort events = 0; + + if (FD_ISSET(fd, rfds)) { + events |= G_IO_IN | G_IO_HUP | G_IO_ERR; + FD_CLR(fd, rfds); + } + + if (FD_ISSET(fd, wfds)) { + events |= G_IO_OUT | G_IO_ERR; + FD_CLR(fd, wfds); + } + + if (FD_ISSET(fd, efds)) { + events |= G_IO_HUP | G_IO_ERR; + FD_CLR(fd, efds); + } + + return events; +} + +/** + * Updates all registered GPollFD objects, unregisters old ones, + * registers new ones. + * + * Runs in the I/O thread. No lock needed. + */ +void +CurlSockets::UpdateSockets() +{ + assert(io_thread_inside()); + + fd_set rfds, wfds, efds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + int max_fd; + CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, + &efds, &max_fd); + if (mcode != CURLM_OK) { + FormatError(curlm_domain, + "curl_multi_fdset() failed: %s", + curl_multi_strerror(mcode)); + return; + } + + UpdateSocketList([&rfds, &wfds, &efds](int fd){ + return input_curl_fd_events(fd, &rfds, + &wfds, &efds); + }); + + for (int fd = 0; fd <= max_fd; ++fd) { + unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) + AddSocket(fd, events); + } +} + +/** + * Runs in the I/O thread. No lock needed. + */ +static bool +input_curl_easy_add(struct input_curl *c, Error &error) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy != NULL); + assert(input_curl_find_request(c->easy) == NULL); + + curl.requests.push_front(c); + + CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); + if (mcode != CURLM_OK) { + error.Format(curlm_domain, mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + return false; + } + + curl.sockets->InvalidateSockets(); + + return true; +} + +/** + * Call input_curl_easy_add() in the I/O thread. May be called from + * any thread. Caller must not hold a mutex. + */ +static bool +input_curl_easy_add_indirect(struct input_curl *c, Error &error) +{ + assert(c != NULL); + assert(c->easy != NULL); + + bool result; + BlockingCall(io_thread_get(), [c, &error, &result](){ + result = input_curl_easy_add(c, error); + }); + return result; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * Runs in the I/O thread. + */ +static void +input_curl_easy_free(struct input_curl *c) +{ + assert(io_thread_inside()); + assert(c != NULL); + + if (c->easy == NULL) + return; + + curl.requests.remove(c); + + curl_multi_remove_handle(curl.multi, c->easy); + curl_easy_cleanup(c->easy); + c->easy = NULL; + + curl_slist_free_all(c->request_headers); + c->request_headers = NULL; + + g_free(c->range); + c->range = NULL; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The mutex must not be locked. + */ +static void +input_curl_easy_free_indirect(struct input_curl *c) +{ + BlockingCall(io_thread_get(), [c](){ + input_curl_easy_free(c); + curl.sockets->InvalidateSockets(); + }); + + assert(c->easy == NULL); +} + +/** + * Abort and free all HTTP requests. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_abort_all_requests(const Error &error) +{ + assert(io_thread_inside()); + assert(error.IsDefined()); + + while (!curl.requests.empty()) { + struct input_curl *c = curl.requests.front(); + assert(!c->postponed_error.IsDefined()); + + input_curl_easy_free(c); + + const ScopeLock protect(c->base.mutex); + + c->postponed_error.Set(error); + c->base.ready = true; + + c->base.cond.broadcast(); + } + +} + +/** + * A HTTP request is finished. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_request_done(struct input_curl *c, CURLcode result, long status) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy == NULL); + assert(!c->postponed_error.IsDefined()); + + const ScopeLock protect(c->base.mutex); + + if (result != CURLE_OK) { + c->postponed_error.Format(curl_domain, result, + "curl failed: %s", c->error); + } else if (status < 200 || status >= 300) { + c->postponed_error.Format(http_domain, status, + "got HTTP status %ld", + status); + } + + c->base.ready = true; + + c->base.cond.broadcast(); +} + +static void +input_curl_handle_done(CURL *easy_handle, CURLcode result) +{ + struct input_curl *c = input_curl_find_request(easy_handle); + assert(c != NULL); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + input_curl_easy_free(c); + input_curl_request_done(c, result, status); +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_info_read(void) +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(curl.multi, + &msgs_in_queue)) != NULL) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +/** + * Give control to CURL. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static bool +input_curl_perform(void) +{ + assert(io_thread_inside()); + + CURLMcode mcode; + + do { + int running_handles; + mcode = curl_multi_perform(curl.multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + Error error; + error.Format(curlm_domain, mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); + input_curl_abort_all_requests(error); + return false; + } + + return true; +} + +int +CurlSockets::PrepareSockets() +{ + UpdateSockets(); + + long timeout2; + CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); + if (mcode == CURLM_OK) { + if (timeout2 >= 0 && timeout2 < 10) + /* CURL 7.21.1 likes to report "timeout=0", + which means we're running in a busy loop. + Quite a bad idea to waste so much CPU. + Let's use a lower limit of 10ms. */ + timeout2 = 10; + + return timeout2; + } else { + FormatWarning(curlm_domain, + "curl_multi_timeout() failed: %s", + curl_multi_strerror(mcode)); + return -1; + } +} + +void +CurlSockets::DispatchSockets() +{ + if (input_curl_perform()) + input_curl_info_read(); +} + +/* + * input_plugin methods + * + */ + +static bool +input_curl_init(const config_param ¶m, Error &error) +{ + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code != CURLE_OK) { + error.Format(curl_domain, code, + "curl_global_init() failed: %s", + curl_easy_strerror(code)); + return false; + } + + http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); + + proxy = param.GetBlockValue("proxy"); + proxy_port = param.GetBlockValue("proxy_port", 0u); + proxy_user = param.GetBlockValue("proxy_user"); + proxy_password = param.GetBlockValue("proxy_password"); + + if (proxy == NULL) { + /* deprecated proxy configuration */ + proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL); + proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); + proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL); + proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, + ""); + } + + curl.multi = curl_multi_init(); + if (curl.multi == NULL) { + error.Set(curl_domain, 0, "curl_multi_init() failed"); + return false; + } + + curl.sockets = new CurlSockets(io_thread_get()); + + return true; +} + +static void +input_curl_finish(void) +{ + assert(curl.requests.empty()); + + BlockingCall(io_thread_get(), [](){ + delete curl.sockets; + }); + + curl_multi_cleanup(curl.multi); + + curl_slist_free_all(http_200_aliases); + + curl_global_cleanup(); +} + +/** + * Determine the total sizes of all buffers, including portions that + * have already been consumed. + * + * The caller must lock the mutex. + */ +gcc_pure +static size_t +curl_total_buffer_size(const struct input_curl *c) +{ + size_t total = 0; + + for (const auto &i : c->buffers) + total += i.TotalSize(); + + return total; +} + +input_curl::~input_curl() +{ + delete tag; + + g_free(meta_name); + + input_curl_easy_free_indirect(this); +} + +static bool +input_curl_check(struct input_stream *is, Error &error) +{ + struct input_curl *c = (struct input_curl *)is; + + bool success = !c->postponed_error.IsDefined(); + if (!success) { + error = std::move(c->postponed_error); + c->postponed_error.Clear(); + } + + return success; +} + +static Tag * +input_curl_tag(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + Tag *tag = c->tag; + + c->tag = NULL; + return tag; +} + +static bool +fill_buffer(struct input_curl *c, Error &error) +{ + while (c->easy != NULL && c->buffers.empty()) + c->base.cond.wait(c->base.mutex); + + if (c->postponed_error.IsDefined()) { + error = std::move(c->postponed_error); + c->postponed_error.Clear(); + return false; + } + + return !c->buffers.empty(); +} + +static size_t +read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers, + void *dest0, size_t length) +{ + auto &buffer = buffers.front(); + uint8_t *dest = (uint8_t *)dest0; + size_t nbytes = 0; + + if (length > buffer.Available()) + length = buffer.Available(); + + while (true) { + size_t chunk; + + chunk = icy.Data(length); + if (chunk > 0) { + const bool empty = !buffer.Read(dest, chunk); + + nbytes += chunk; + dest += chunk; + length -= chunk; + + if (empty) { + buffers.pop_front(); + break; + } + + if (length == 0) + break; + } + + chunk = icy.Meta(buffer.Begin(), length); + if (chunk > 0) { + const bool empty = !buffer.Consume(chunk); + + length -= chunk; + + if (empty) { + buffers.pop_front(); + break; + } + + if (length == 0) + break; + } + } + + return nbytes; +} + +static void +copy_icy_tag(struct input_curl *c) +{ + Tag *tag = c->icy.ReadTag(); + + if (tag == NULL) + return; + + delete c->tag; + + if (c->meta_name != NULL && !tag->HasType(TAG_NAME)) + tag->AddItem(TAG_NAME, c->meta_name); + + c->tag = tag; +} + +static bool +input_curl_available(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->postponed_error.IsDefined() || c->easy == NULL || + !c->buffers.empty(); +} + +static size_t +input_curl_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + struct input_curl *c = (struct input_curl *)is; + bool success; + size_t nbytes = 0; + char *dest = (char *)ptr; + + do { + /* fill the buffer */ + + success = fill_buffer(c, error); + if (!success) + return 0; + + /* send buffer contents */ + + while (size > 0 && !c->buffers.empty()) { + size_t copy = read_from_buffer(c->icy, c->buffers, + dest + nbytes, size); + + nbytes += copy; + size -= copy; + } + } while (nbytes == 0); + + if (c->icy.IsDefined()) + copy_icy_tag(c); + + is->offset += (goffset)nbytes; + + if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { + c->base.mutex.unlock(); + + BlockingCall(io_thread_get(), [c](){ + input_curl_resume(c); + }); + + c->base.mutex.lock(); + } + + return nbytes; +} + +static void +input_curl_close(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + delete c; +} + +static bool +input_curl_eof(gcc_unused struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->easy == NULL && c->buffers.empty(); +} + +/** called by curl when new data is available */ +static size_t +input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_curl *c = (struct input_curl *)stream; + char name[64]; + + size *= nmemb; + + const char *header = (const char *)ptr; + const char *end = header + size; + + const char *value = (const char *)memchr(header, ':', size); + if (value == NULL || (size_t)(value - header) >= sizeof(name)) + return size; + + memcpy(name, header, value - header); + name[value - header] = 0; + + /* skip the colon */ + + ++value; + + /* strip the value */ + + while (value < end && g_ascii_isspace(*value)) + ++value; + + while (end > value && g_ascii_isspace(end[-1])) + --end; + + if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { + /* a stream with icy-metadata is not seekable */ + if (!c->icy.IsDefined()) + c->base.seekable = true; + } else if (g_ascii_strcasecmp(name, "content-length") == 0) { + char buffer[64]; + + if ((size_t)(end - header) >= sizeof(buffer)) + return size; + + memcpy(buffer, value, end - value); + buffer[end - value] = 0; + + c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); + } else if (g_ascii_strcasecmp(name, "content-type") == 0) { + c->base.mime.assign(value, end); + } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || + g_ascii_strcasecmp(name, "ice-name") == 0 || + g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { + g_free(c->meta_name); + c->meta_name = g_strndup(value, end - value); + + delete c->tag; + + c->tag = new Tag(); + c->tag->AddItem(TAG_NAME, c->meta_name); + } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { + char buffer[64]; + size_t icy_metaint; + + if ((size_t)(end - header) >= sizeof(buffer) || + c->icy.IsDefined()) + return size; + + memcpy(buffer, value, end - value); + buffer[end - value] = 0; + + icy_metaint = g_ascii_strtoull(buffer, NULL, 10); + FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint); + + if (icy_metaint > 0) { + c->icy.Start(icy_metaint); + + /* a stream with icy-metadata is not + seekable */ + c->base.seekable = false; + } + } + + return size; +} + +/** called by curl when new data is available */ +static size_t +input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_curl *c = (struct input_curl *)stream; + + size *= nmemb; + if (size == 0) + return 0; + + const ScopeLock protect(c->base.mutex); + + if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { + c->paused = true; + return CURL_WRITEFUNC_PAUSE; + } + + c->buffers.emplace_back(ptr, size); + c->base.ready = true; + + c->base.cond.broadcast(); + return size; +} + +static bool +input_curl_easy_init(struct input_curl *c, Error &error) +{ + CURLcode code; + + c->easy = curl_easy_init(); + if (c->easy == NULL) { + error.Set(curl_domain, "curl_easy_init() failed"); + 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, c); + curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, + input_curl_writefunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); + curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); + curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); + curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); + curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); + curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); + curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); + curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); + curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); + + if (proxy != NULL) + curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); + + if (proxy_port > 0) + curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); + + if (proxy_user != NULL && proxy_password != NULL) { + char *proxy_auth_str = + g_strconcat(proxy_user, ":", proxy_password, NULL); + curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); + g_free(proxy_auth_str); + } + + code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str()); + if (code != CURLE_OK) { + error.Format(curl_domain, 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, + "Icy-Metadata: 1"); + curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); + + return true; +} + +static bool +input_curl_seek(struct input_stream *is, goffset offset, int whence, + Error &error) +{ + struct input_curl *c = (struct input_curl *)is; + bool ret; + + assert(is->ready); + + if (whence == SEEK_SET && offset == is->offset) + /* no-op */ + return true; + + if (!is->seekable) + return false; + + /* calculate the absolute offset */ + + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is->offset; + break; + + case SEEK_END: + if (is->size < 0) + /* stream size is not known */ + return false; + + offset += is->size; + break; + + default: + return false; + } + + if (offset < 0) + return false; + + /* check if we can fast-forward the buffer */ + + while (offset > is->offset && !c->buffers.empty()) { + auto &buffer = c->buffers.front(); + size_t length = buffer.Available(); + if (offset - is->offset < (goffset)length) + length = offset - is->offset; + + const bool empty = !buffer.Consume(length); + if (empty) + c->buffers.pop_front(); + + is->offset += length; + } + + if (offset == is->offset) + return true; + + /* close the old connection and open a new one */ + + c->base.mutex.unlock(); + + input_curl_easy_free_indirect(c); + c->buffers.clear(); + + is->offset = offset; + if (is->offset == is->size) { + /* seek to EOF: simulate empty result; avoid + triggering a "416 Requested Range Not Satisfiable" + response */ + return true; + } + + ret = input_curl_easy_init(c, error); + if (!ret) + return false; + + /* send the "Range" header */ + + if (is->offset > 0) { + c->range = g_strdup_printf("%lld-", (long long)is->offset); + curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); + } + + c->base.ready = false; + + if (!input_curl_easy_add_indirect(c, error)) + return false; + + c->base.mutex.lock(); + + while (!c->base.ready) + c->base.cond.wait(c->base.mutex); + + if (c->postponed_error.IsDefined()) { + error = std::move(c->postponed_error); + c->postponed_error.Clear(); + return false; + } + + return true; +} + +static struct input_stream * +input_curl_open(const char *url, Mutex &mutex, Cond &cond, + Error &error) +{ + if ((strncmp(url, "http://", 7) != 0) && + (strncmp(url, "https://", 8) != 0)) + return NULL; + + struct input_curl *c = new input_curl(url, mutex, cond); + + if (!input_curl_easy_init(c, error)) { + delete c; + return NULL; + } + + if (!input_curl_easy_add_indirect(c, error)) { + delete c; + return NULL; + } + + return &c->base; +} + +const struct input_plugin input_plugin_curl = { + "curl", + input_curl_init, + input_curl_finish, + input_curl_open, + input_curl_close, + input_curl_check, + nullptr, + input_curl_tag, + input_curl_available, + input_curl_read, + input_curl_eof, + input_curl_seek, +}; diff --git a/src/input/CurlInputPlugin.hxx b/src/input/CurlInputPlugin.hxx new file mode 100644 index 000000000..20d1309d8 --- /dev/null +++ b/src/input/CurlInputPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CURL_HXX +#define MPD_INPUT_CURL_HXX + +struct input_stream; + +extern const struct input_plugin input_plugin_curl; + +#endif diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx new file mode 100644 index 000000000..b0665e659 --- /dev/null +++ b/src/input/DespotifyInputPlugin.cxx @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DespotifyInputPlugin.hxx" +#include "DespotifyUtils.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "tag/Tag.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <glib.h> + +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <stdio.h> + +struct DespotifyInputStream { + struct input_stream base; + + struct despotify_session *session; + struct ds_track *track; + Tag *tag; + struct ds_pcm_data pcm; + size_t len_available; + bool eof; + + DespotifyInputStream(const char *uri, + Mutex &mutex, Cond &cond, + despotify_session *_session, + ds_track *_track) + :base(input_plugin_despotify, uri, mutex, cond), + session(_session), track(_track), + tag(mpd_despotify_tag_from_track(track)), + len_available(0), eof(false) { + + memset(&pcm, 0, sizeof(pcm)); + + /* Despotify outputs pcm data */ + base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + base.ready = true; + } + + ~DespotifyInputStream() { + delete tag; + + despotify_free_track(track); + } +}; + +static void +refill_buffer(DespotifyInputStream *ctx) +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(ctx->session, &ctx->pcm); + + if (rc == 0 && ctx->pcm.len) { + ctx->len_available = ctx->pcm.len; + break; + } + if (ctx->eof == true) + break; + + if (rc < 0) { + LogDebug(despotify_domain, "despotify_get_pcm error"); + ctx->eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +static void callback(gcc_unused struct despotify_session* ds, + int sig, gcc_unused void* data, void* callback_data) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; + + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + LogWarning(despotify_domain, "Track play error"); + ctx->eof = true; + ctx->len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + ctx->eof = true; + FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof); + break; + } +} + + +static struct input_stream * +input_despotify_open(const char *url, + Mutex &mutex, Cond &cond, + gcc_unused Error &error) +{ + struct despotify_session *session; + struct ds_link *ds_link; + struct ds_track *track; + + if (!g_str_has_prefix(url, "spt://")) + return NULL; + + session = mpd_despotify_get_session(); + if (!session) + return NULL; + + ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + FormatDebug(despotify_domain, "Can't find %s", url); + return NULL; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return NULL; + } + + track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) + return NULL; + + DespotifyInputStream *ctx = + new DespotifyInputStream(url, mutex, cond, + session, track); + + if (!mpd_despotify_register_callback(callback, ctx)) { + delete ctx; + return NULL; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + mpd_despotify_unregister_callback(callback); + delete ctx; + return NULL; + } + + return &ctx->base; +} + +static size_t +input_despotify_read(struct input_stream *is, void *ptr, size_t size, + gcc_unused Error &error) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + size_t to_cpy = size; + + if (ctx->len_available == 0) + refill_buffer(ctx); + + if (ctx->len_available < size) + to_cpy = ctx->len_available; + memcpy(ptr, ctx->pcm.buf, to_cpy); + ctx->len_available -= to_cpy; + + is->offset += to_cpy; + + return to_cpy; +} + +static void +input_despotify_close(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + mpd_despotify_unregister_callback(callback); + delete ctx; +} + +static bool +input_despotify_eof(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + return ctx->eof; +} + +static Tag * +input_despotify_tag(struct input_stream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + Tag *tag = ctx->tag; + + ctx->tag = NULL; + + return tag; +} + +const struct input_plugin input_plugin_despotify = { + "spt", + nullptr, + nullptr, + input_despotify_open, + input_despotify_close, + nullptr, + nullptr, + input_despotify_tag, + nullptr, + input_despotify_read, + input_despotify_eof, + nullptr, +}; diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx new file mode 100644 index 000000000..00d699408 --- /dev/null +++ b/src/input/DespotifyInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INPUT_DESPOTIFY_HXX +#define INPUT_DESPOTIFY_HXX + +extern const struct input_plugin input_plugin_despotify; + +#endif diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx new file mode 100644 index 000000000..f8b948c43 --- /dev/null +++ b/src/input/FfmpegInputPlugin.cxx @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +extern "C" { +#include <libavutil/avutil.h> +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +} + +struct FfmpegInputStream { + struct input_stream base; + + AVIOContext *h; + + bool eof; + + FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond, + AVIOContext *_h) + :base(input_plugin_ffmpeg, uri, mutex, cond), + h(_h), eof(false) { + base.ready = true; + base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0; + base.size = avio_size(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 */ + base.mime = g_strdup("audio/x-mpd-ffmpeg"); + } + + ~FfmpegInputStream() { + avio_close(h); + } +}; + +static constexpr Domain ffmpeg_domain("ffmpeg"); + +static inline bool +input_ffmpeg_supported(void) +{ + void *opaque = nullptr; + return avio_enum_protocols(&opaque, 0) != nullptr; +} + +static bool +input_ffmpeg_init(gcc_unused const config_param ¶m, + Error &error) +{ + av_register_all(); + + /* disable this plugin if there's no registered protocol */ + if (!input_ffmpeg_supported()) { + error.Set(ffmpeg_domain, "No protocol"); + return false; + } + + return true; +} + +static struct input_stream * +input_ffmpeg_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + 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 nullptr; + + AVIOContext *h; + int ret = avio_open(&h, uri, AVIO_FLAG_READ); + if (ret != 0) { + error.Set(ffmpeg_domain, ret, + "libavformat failed to open the URI"); + return nullptr; + } + + auto *i = new FfmpegInputStream(uri, mutex, cond, h); + return &i->base; +} + +static size_t +input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + int ret = avio_read(i->h, (unsigned char *)ptr, size); + if (ret <= 0) { + if (ret < 0) + error.Set(ffmpeg_domain, "avio_read() failed"); + + i->eof = true; + return false; + } + + is->offset += ret; + return (size_t)ret; +} + +static void +input_ffmpeg_close(struct input_stream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + delete i; +} + +static bool +input_ffmpeg_eof(struct input_stream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + return i->eof; +} + +static bool +input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, + Error &error) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + int64_t ret = avio_seek(i->h, offset, whence); + + if (ret >= 0) { + i->eof = false; + return true; + } else { + error.Set(ffmpeg_domain, "avio_seek() failed"); + return false; + } +} + +const struct input_plugin input_plugin_ffmpeg = { + "ffmpeg", + input_ffmpeg_init, + nullptr, + input_ffmpeg_open, + input_ffmpeg_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_ffmpeg_read, + input_ffmpeg_eof, + input_ffmpeg_seek, +}; diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/FfmpegInputPlugin.hxx new file mode 100644 index 000000000..d5e3a8d9b --- /dev/null +++ b/src/input/FfmpegInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_FFMPEG_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libavformat's "avio" library. + */ +extern const struct input_plugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx new file mode 100644 index 000000000..75103f711 --- /dev/null +++ b/src/input/FileInputPlugin.cxx @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FileInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/fd_util.h" +#include "open.h" + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <glib.h> + +static constexpr Domain file_domain("file"); + +struct FileInputStream { + struct input_stream base; + + int fd; + + FileInputStream(const char *path, int _fd, off_t size, + Mutex &mutex, Cond &cond) + :base(input_plugin_file, path, mutex, cond), + fd(_fd) { + base.size = size; + base.seekable = true; + base.ready = true; + } + + ~FileInputStream() { + close(fd); + } +}; + +static struct input_stream * +input_file_open(const char *filename, + Mutex &mutex, Cond &cond, + Error &error) +{ + int fd, ret; + struct stat st; + + if (!g_path_is_absolute(filename)) + return nullptr; + + fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); + if (fd < 0) { + if (errno != ENOENT && errno != ENOTDIR) + error.FormatErrno("Failed to open \"%s\"", + filename); + return nullptr; + } + + ret = fstat(fd, &st); + if (ret < 0) { + error.FormatErrno("Failed to stat \"%s\"", filename); + close(fd); + return nullptr; + } + + if (!S_ISREG(st.st_mode)) { + error.Format(file_domain, "Not a regular file: %s", filename); + close(fd); + return nullptr; + } + +#ifdef POSIX_FADV_SEQUENTIAL + posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); +#endif + + FileInputStream *fis = new FileInputStream(filename, fd, st.st_size, + mutex, cond); + return &fis->base; +} + +static bool +input_file_seek(struct input_stream *is, goffset offset, int whence, + Error &error) +{ + FileInputStream *fis = (FileInputStream *)is; + + offset = (goffset)lseek(fis->fd, (off_t)offset, whence); + if (offset < 0) { + error.SetErrno("Failed to seek"); + return false; + } + + is->offset = offset; + return true; +} + +static size_t +input_file_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + FileInputStream *fis = (FileInputStream *)is; + ssize_t nbytes; + + nbytes = read(fis->fd, ptr, size); + if (nbytes < 0) { + error.SetErrno("Failed to read"); + return 0; + } + + is->offset += nbytes; + return (size_t)nbytes; +} + +static void +input_file_close(struct input_stream *is) +{ + FileInputStream *fis = (FileInputStream *)is; + + delete fis; +} + +static bool +input_file_eof(struct input_stream *is) +{ + return is->offset >= is->size; +} + +const struct input_plugin input_plugin_file = { + "file", + nullptr, + nullptr, + input_file_open, + input_file_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_file_read, + input_file_eof, + input_file_seek, +}; diff --git a/src/input/FileInputPlugin.hxx b/src/input/FileInputPlugin.hxx new file mode 100644 index 000000000..aacfd0b5d --- /dev/null +++ b/src/input/FileInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FILE_HXX +#define MPD_INPUT_FILE_HXX + +extern const struct input_plugin input_plugin_file; + +#endif diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx new file mode 100644 index 000000000..15c6ac377 --- /dev/null +++ b/src/input/MmsInputPlugin.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MmsInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> +#include <libmms/mmsx.h> + +#include <string.h> +#include <errno.h> + +struct MmsInputStream { + struct input_stream base; + + mmsx_t *mms; + + bool eof; + + MmsInputStream(const char *uri, + Mutex &mutex, Cond &cond, + mmsx_t *_mms) + :base(input_plugin_mms, uri, mutex, cond), + mms(_mms), eof(false) { + /* XX is this correct? at least this selects the ffmpeg + decoder, which seems to work fine*/ + base.mime = g_strdup("audio/x-ms-wma"); + + base.ready = true; + } + + ~MmsInputStream() { + mmsx_close(mms); + } +}; + +static constexpr Domain mms_domain("mms"); + +static struct input_stream * +input_mms_open(const char *url, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!g_str_has_prefix(url, "mms://") && + !g_str_has_prefix(url, "mmsh://") && + !g_str_has_prefix(url, "mmst://") && + !g_str_has_prefix(url, "mmsu://")) + return nullptr; + + const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024); + if (mms == nullptr) { + error.Set(mms_domain, "mmsx_connect() failed"); + return nullptr; + } + + auto m = new MmsInputStream(url, mutex, cond, mms); + return &m->base; +} + +static size_t +input_mms_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + MmsInputStream *m = (MmsInputStream *)is; + int ret; + + ret = mmsx_read(nullptr, m->mms, (char *)ptr, size); + if (ret <= 0) { + if (ret < 0) + error.SetErrno("mmsx_read() failed"); + + m->eof = true; + return false; + } + + is->offset += ret; + + return (size_t)ret; +} + +static void +input_mms_close(struct input_stream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + delete m; +} + +static bool +input_mms_eof(struct input_stream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + return m->eof; +} + +const struct input_plugin input_plugin_mms = { + "mms", + nullptr, + nullptr, + input_mms_open, + input_mms_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_mms_read, + input_mms_eof, + nullptr, +}; diff --git a/src/input/mms_input_plugin.h b/src/input/MmsInputPlugin.hxx index d6aa593f2..d6aa593f2 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/MmsInputPlugin.hxx diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx new file mode 100644 index 000000000..a49cf7663 --- /dev/null +++ b/src/input/RewindInputPlugin.cxx @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "RewindInputPlugin.hxx" +#include "InputInternal.hxx" +#include "InputStream.hxx" +#include "InputPlugin.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +extern const struct input_plugin rewind_input_plugin; + +struct RewindInputStream { + struct input_stream base; + + struct input_stream *input; + + /** + * The read position within the buffer. Undefined as long as + * ReadingFromBuffer() returns false. + */ + size_t head; + + /** + * The write/append position within the buffer. + */ + size_t tail; + + /** + * The size of this buffer is the maximum number of bytes + * which can be rewinded cheaply without passing the "seek" + * call to CURL. + * + * The origin of this buffer is always the beginning of the + * stream (offset 0). + */ + char buffer[64 * 1024]; + + RewindInputStream(input_stream *_input) + :base(rewind_input_plugin, _input->uri.c_str(), + _input->mutex, _input->cond), + input(_input), tail(0) { + } + + ~RewindInputStream() { + input->Close(); + } + + /** + * Are we currently reading from the buffer, and does the + * buffer contain more data for the next read operation? + */ + bool ReadingFromBuffer() const { + return tail > 0 && base.offset < input->offset; + } + + /** + * Copy public attributes from the underlying input stream to the + * "rewind" input stream. This function is called when a method of + * the underlying stream has returned, which may have modified these + * attributes. + */ + void CopyAttributes() { + struct input_stream *dest = &base; + const struct input_stream *src = input; + + assert(dest != src); + + bool dest_ready = dest->ready; + + dest->ready = src->ready; + dest->seekable = src->seekable; + dest->size = src->size; + dest->offset = src->offset; + + if (!dest_ready && src->ready) + dest->mime = src->mime; + } +}; + +static void +input_rewind_close(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + delete r; +} + +static bool +input_rewind_check(struct input_stream *is, Error &error) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return r->input->Check(error); +} + +static void +input_rewind_update(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (!r->ReadingFromBuffer()) + r->CopyAttributes(); +} + +static Tag * +input_rewind_tag(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return r->input->ReadTag(); +} + +static bool +input_rewind_available(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return r->input->IsAvailable(); +} + +static size_t +input_rewind_read(struct input_stream *is, void *ptr, size_t size, + Error &error) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (r->ReadingFromBuffer()) { + /* buffered read */ + + assert(r->head == (size_t)is->offset); + assert(r->tail == (size_t)r->input->offset); + + if (size > r->tail - r->head) + size = r->tail - r->head; + + memcpy(ptr, r->buffer + r->head, size); + r->head += size; + is->offset += size; + + return size; + } else { + /* pass method call to underlying stream */ + + size_t nbytes = r->input->Read(ptr, size, error); + + if (r->input->offset > (goffset)sizeof(r->buffer)) + /* disable buffering */ + r->tail = 0; + else if (r->tail == (size_t)is->offset) { + /* append to buffer */ + + memcpy(r->buffer + r->tail, ptr, nbytes); + r->tail += nbytes; + + assert(r->tail == (size_t)r->input->offset); + } + + r->CopyAttributes(); + + return nbytes; + } +} + +static bool +input_rewind_eof(struct input_stream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return !r->ReadingFromBuffer() && r->input->IsEOF(); +} + +static bool +input_rewind_seek(struct input_stream *is, goffset offset, int whence, + Error &error) +{ + RewindInputStream *r = (RewindInputStream *)is; + + assert(is->ready); + + if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { + /* buffered seek */ + + assert(!r->ReadingFromBuffer() || + r->head == (size_t)is->offset); + assert(r->tail == (size_t)r->input->offset); + + r->head = (size_t)offset; + is->offset = offset; + + return true; + } else { + bool success = r->input->Seek(offset, whence, error); + r->CopyAttributes(); + + /* disable the buffer, because r->input has left the + buffered range now */ + r->tail = 0; + + return success; + } +} + +const struct input_plugin rewind_input_plugin = { + nullptr, + nullptr, + nullptr, + nullptr, + input_rewind_close, + input_rewind_check, + input_rewind_update, + input_rewind_tag, + input_rewind_available, + input_rewind_read, + input_rewind_eof, + input_rewind_seek, +}; + +struct input_stream * +input_rewind_open(struct input_stream *is) +{ + assert(is != NULL); + assert(is->offset == 0); + + if (is->seekable) + /* seekable resources don't need this plugin */ + return is; + + RewindInputStream *c = new RewindInputStream(is); + return &c->base; +} diff --git a/src/input/RewindInputPlugin.hxx b/src/input/RewindInputPlugin.hxx new file mode 100644 index 000000000..cf21e92f1 --- /dev/null +++ b/src/input/RewindInputPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * A wrapper for an input_stream object which allows cheap buffered + * rewinding. This is useful while detecting the stream codec (let + * each decoder plugin peek a portion from the stream). + */ + +#ifndef MPD_INPUT_REWIND_HXX +#define MPD_INPUT_REWIND_HXX + +#include "check.h" + +struct input_stream; + +struct input_stream * +input_rewind_open(struct input_stream *is); + +#endif diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c deleted file mode 100644 index 4a038b9e2..000000000 --- a/src/input/archive_input_plugin.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/archive_input_plugin.h" -#include "archive_api.h" -#include "archive_list.h" -#include "input_plugin.h" - -#include <glib.h> - -/** - * select correct archive plugin to handle the input stream - * may allow stacking of archive plugins. for example for handling - * tar.gz a gzip handler opens file (through inputfile stream) - * then it opens a tar handler and sets gzip inputstream as - * parent_stream so tar plugin fetches file data from gzip - * plugin and gzip fetches file from disk - */ -static struct input_stream * -input_archive_open(const char *pathname, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - const struct archive_plugin *arplug; - struct archive_file *file; - char *archive, *filename, *suffix, *pname; - struct input_stream *is; - - 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 NULL; - } - - //check which archive plugin to use (by ext) - arplug = archive_plugin_from_suffix(suffix); - if (!arplug) { - g_warning("can't handle archive %s\n",archive); - g_free(pname); - return NULL; - } - - file = archive_file_open(arplug, archive, error_r); - if (file == NULL) - return NULL; - - //setup fileops - is = archive_file_open_stream(file, filename, mutex, cond, - error_r); - archive_file_close(file); - g_free(pname); - - return is; -} - -const struct input_plugin input_plugin_archive = { - .name = "archive", - .open = input_archive_open, -}; diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h deleted file mode 100644 index 51095f37f..000000000 --- a/src/input/archive_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_ARCHIVE_H -#define MPD_INPUT_ARCHIVE_H - -extern const struct input_plugin input_plugin_archive; - -#endif diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c deleted file mode 100644 index 1de7623a1..000000000 --- a/src/input/cdio_paranoia_input_plugin.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * CD-Audio handling (requires libcdio_paranoia) - */ - -#include "config.h" -#include "input/cdio_paranoia_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "refcount.h" - -#include <stdio.h> -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> -#include <assert.h> - -#include <cdio/paranoia.h> -#include <cdio/cd_types.h> - -struct input_cdio_paranoia { - struct input_stream base; - - cdrom_drive_t *drv; - CdIo_t *cdio; - cdrom_paranoia_t *para; - - lsn_t lsn_from, lsn_to; - int lsn_relofs; - - int trackno; - - char buffer[CDIO_CD_FRAMESIZE_RAW]; - int buffer_lsn; -}; - -static inline GQuark -cdio_quark(void) -{ - return g_quark_from_static_string("cdio"); -} - -static void -input_cdio_close(struct input_stream *is) -{ - struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is; - - if (i->para) - cdio_paranoia_free(i->para); - if (i->drv) - cdio_cddap_close_no_free_cdio( i->drv); - if (i->cdio) - cdio_destroy( i->cdio ); - - input_stream_deinit(&i->base); - g_free(i); -} - -struct cdio_uri { - char device[64]; - int track; -}; - -static bool -parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) -{ - if (!g_str_has_prefix(src, "cdda://")) - return false; - - src += 7; - - if (*src == 0) { - /* play the whole CD in the default drive */ - dest->device[0] = 0; - dest->track = -1; - return true; - } - - const char *slash = strrchr(src, '/'); - if (slash == NULL) { - /* play the whole CD in the specified drive */ - g_strlcpy(dest->device, src, sizeof(dest->device)); - dest->track = -1; - return true; - } - - size_t device_length = slash - src; - if (device_length >= sizeof(dest->device)) - device_length = sizeof(dest->device) - 1; - - memcpy(dest->device, src, device_length); - dest->device[device_length] = 0; - - const char *track = slash + 1; - - char *endptr; - dest->track = strtoul(track, &endptr, 10); - if (*endptr != 0) { - g_set_error(error_r, cdio_quark(), 0, - "Malformed track number"); - return false; - } - - if (endptr == track) - /* play the whole CD */ - dest->track = -1; - - return true; -} - -static char * -cdio_detect_device(void) -{ - char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); - if (devices == NULL) - return NULL; - - char *device = g_strdup(devices[0]); - cdio_free_device_list(devices); - - return device; -} - -static struct input_stream * -input_cdio_open(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_cdio_paranoia *i; - - struct cdio_uri parsed_uri; - if (!parse_cdio_uri(&parsed_uri, uri, error_r)) - return NULL; - - i = g_new(struct input_cdio_paranoia, 1); - input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri, - mutex, cond); - - /* initialize everything (should be already) */ - i->drv = NULL; - i->cdio = NULL; - i->para = NULL; - i->trackno = parsed_uri.track; - - /* get list of CD's supporting CD-DA */ - char *device = parsed_uri.device[0] != 0 - ? g_strdup(parsed_uri.device) - : cdio_detect_device(); - if (device == NULL) { - g_set_error(error_r, cdio_quark(), 0, - "Unable find or access a CD-ROM drive with an audio CD in it."); - input_cdio_close(&i->base); - return NULL; - } - - /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ - i->cdio = cdio_open(device, DRIVER_UNKNOWN); - g_free(device); - - i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL); - - if ( !i->drv ) { - g_set_error(error_r, cdio_quark(), 0, - "Unable to identify audio CD disc."); - input_cdio_close(&i->base); - return NULL; - } - - cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); - - if ( 0 != cdio_cddap_open(i->drv) ) { - g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); - input_cdio_close(&i->base); - return NULL; - } - - bool reverse_endian; - switch (data_bigendianp(i->drv)) { - case -1: - g_debug("cdda: drive returns unknown audio data"); - reverse_endian = false; - break; - case 0: - g_debug("cdda: drive returns audio data Little Endian."); - reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; - break; - case 1: - g_debug("cdda: drive returns audio data Big Endian."); - reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; - break; - default: - g_set_error(error_r, cdio_quark(), 0, - "Drive returns unknown data type %d", - data_bigendianp(i->drv)); - input_cdio_close(&i->base); - return NULL; - } - - i->lsn_relofs = 0; - - if (i->trackno >= 0) { - i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); - i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); - } else { - i->lsn_from = 0; - i->lsn_to = cdio_get_disc_last_lsn(i->cdio); - } - - i->para = cdio_paranoia_init(i->drv); - - /* Set reading mode for full paranoia, but allow skipping sectors. */ - paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); - - /* seek to beginning of the track */ - cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); - - i->base.ready = true; - i->base.seekable = true; - i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; - - /* hack to make MPD select the "pcm" decoder plugin */ - i->base.mime = g_strdup(reverse_endian - ? "audio/x-mpd-cdda-pcm-reverse" - : "audio/x-mpd-cdda-pcm"); - - return &i->base; -} - -static bool -input_cdio_seek(struct input_stream *is, - goffset offset, int whence, GError **error_r) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - - /* calculate absolute offset */ - switch (whence) { - case SEEK_SET: - break; - case SEEK_CUR: - offset += cis->base.offset; - break; - case SEEK_END: - offset += cis->base.size; - break; - } - - if (offset < 0 || offset > cis->base.size) { - g_set_error(error_r, cdio_quark(), 0, - "Invalid offset to seek %ld (%ld)", - (long int)offset, (long int)cis->base.size); - return false; - } - - /* simple case */ - if (offset == cis->base.offset) - return true; - - /* calculate current LSN */ - cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; - cis->base.offset = offset; - - cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); - - return true; -} - -static size_t -input_cdio_read(struct input_stream *is, void *ptr, size_t length, - GError **error_r) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - size_t nbytes = 0; - int diff; - size_t len, maxwrite; - int16_t *rbuf; - char *s_err, *s_mess; - char *wptr = (char *) ptr; - - while (length > 0) { - - - /* end of track ? */ - if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) - break; - - //current sector was changed ? - if (cis->lsn_relofs != cis->buffer_lsn) { - rbuf = cdio_paranoia_read(cis->para, NULL); - - s_err = cdda_errors(cis->drv); - if (s_err) { - g_warning("paranoia_read: %s", s_err ); - free(s_err); - } - s_mess = cdda_messages(cis->drv); - if (s_mess) { - free(s_mess); - } - if (!rbuf) { - g_set_error(error_r, cdio_quark(), 0, - "paranoia read error. Stopping."); - return 0; - } - //store current buffer - memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); - cis->buffer_lsn = cis->lsn_relofs; - } else { - //use cached sector - rbuf = (int16_t*) cis->buffer; - } - - //correct offset - diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; - - assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); - - maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer - len = (length < maxwrite? length : maxwrite); - - //skip diff bytes from this lsn - memcpy(wptr, ((char*)rbuf) + diff, len); - //update pointer - wptr += len; - nbytes += len; - - //update offset - cis->base.offset += len; - cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; - //update length - length -= len; - } - - return nbytes; -} - -static bool -input_cdio_eof(struct input_stream *is) -{ - struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; - - return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); -} - -const struct input_plugin input_plugin_cdio_paranoia = { - .name = "cdio_paranoia", - .open = input_cdio_open, - .close = input_cdio_close, - .seek = input_cdio_seek, - .read = input_cdio_read, - .eof = input_cdio_eof -}; diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h deleted file mode 100644 index 71c5cbe8d..000000000 --- a/src/input/cdio_paranoia_input_plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_H -#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H - -/** - * An input plugin based on libcdio_paranoia library. - */ -extern const struct input_plugin input_plugin_cdio_paranoia; - -#endif diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c deleted file mode 100644 index 3f191141e..000000000 --- a/src/input/curl_input_plugin.c +++ /dev/null @@ -1,1301 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/curl_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "conf.h" -#include "tag.h" -#include "icy_metadata.h" -#include "io_thread.h" -#include "glib_compat.h" - -#include <assert.h> - -#if defined(WIN32) - #include <winsock2.h> -#else - #include <sys/select.h> -#endif - -#include <string.h> -#include <errno.h> - -#include <curl/curl.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_curl" - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t CURL_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t CURL_RESUME_AT = 384 * 1024; - -/** - * Buffers created by input_curl_writefunction(). - */ -struct buffer { - /** size of the payload */ - size_t size; - - /** how much has been consumed yet? */ - size_t consumed; - - /** the payload */ - unsigned char data[sizeof(long)]; -}; - -struct input_curl { - struct input_stream base; - - /* some buffers which were passed to libcurl, which we have - too free */ - char *url, *range; - struct curl_slist *request_headers; - - /** the curl handles */ - CURL *easy; - - /** the GMainLoop source used to poll all CURL file - descriptors */ - GSource *source; - - /** the source id of #source */ - guint source_id; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; - - /** list of buffers, where input_curl_writefunction() appends - to, and input_curl_read() reads from them */ - GQueue *buffers; - -#if LIBCURL_VERSION_NUM >= 0x071200 - /** - * Is the connection currently paused? That happens when the - * buffer was getting too large. It will be unpaused when the - * buffer is below the threshold again. - */ - bool paused; -#endif - - /** error message provided by libcurl */ - char error[CURL_ERROR_SIZE]; - - /** parser for icy-metadata */ - struct icy_metadata icy_metadata; - - /** the stream name from the icy-name response header */ - char *meta_name; - - /** the tag object ready to be requested via - input_stream_tag() */ - struct tag *tag; - - GError *postponed_error; -}; - -/** libcurl should accept "ICY 200 OK" */ -static struct curl_slist *http_200_aliases; - -/** HTTP proxy settings */ -static const char *proxy, *proxy_user, *proxy_password; -static unsigned proxy_port; - -static struct { - CURLM *multi; - - /** - * A linked list of all active HTTP requests. An active - * request is one that doesn't have the "eof" flag set. - */ - GSList *requests; - - /** - * The GMainLoop source used to poll all CURL file - * descriptors. - */ - GSource *source; - - /** - * The source id of #source. - */ - guint source_id; - - GSList *fds; - -#if LIBCURL_VERSION_NUM >= 0x070f04 - /** - * Did CURL give us a timeout? If yes, then we need to call - * curl_multi_perform(), even if there was no event on any - * file descriptor. - */ - bool timeout; - - /** - * The absolute time stamp when the timeout expires. This is - * used in the GSource method check(). - */ - gint64 absolute_timeout; -#endif -} curl; - -static inline GQuark -curl_quark(void) -{ - return g_quark_from_static_string("curl"); -} - -/** - * Find a request by its CURL "easy" handle. - * - * Runs in the I/O thread. No lock needed. - */ -static struct input_curl * -input_curl_find_request(CURL *easy) -{ - assert(io_thread_inside()); - - for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { - struct input_curl *c = i->data; - if (c->easy == easy) - return c; - } - - return NULL; -} - -#if LIBCURL_VERSION_NUM >= 0x071200 - -static gpointer -input_curl_resume(gpointer data) -{ - assert(io_thread_inside()); - - struct input_curl *c = data; - - if (c->paused) { - c->paused = false; - curl_easy_pause(c->easy, CURLPAUSE_CONT); - } - - return NULL; -} - -#endif - -/** - * Calculates the GLib event bit mask for one file descriptor, - * obtained from three #fd_set objects filled by curl_multi_fdset(). - */ -static gushort -input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) -{ - gushort events = 0; - - if (FD_ISSET(fd, rfds)) { - events |= G_IO_IN | G_IO_HUP | G_IO_ERR; - FD_CLR(fd, rfds); - } - - if (FD_ISSET(fd, wfds)) { - events |= G_IO_OUT | G_IO_ERR; - FD_CLR(fd, wfds); - } - - if (FD_ISSET(fd, efds)) { - events |= G_IO_HUP | G_IO_ERR; - FD_CLR(fd, efds); - } - - return events; -} - -/** - * Updates all registered GPollFD objects, unregisters old ones, - * registers new ones. - * - * Runs in the I/O thread. No lock needed. - */ -static void -curl_update_fds(void) -{ - assert(io_thread_inside()); - - fd_set rfds, wfds, efds; - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - int max_fd; - CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, - &efds, &max_fd); - if (mcode != CURLM_OK) { - g_warning("curl_multi_fdset() failed: %s\n", - curl_multi_strerror(mcode)); - return; - } - - GSList *fds = curl.fds; - curl.fds = NULL; - - while (fds != NULL) { - GPollFD *poll_fd = fds->data; - gushort events = input_curl_fd_events(poll_fd->fd, &rfds, - &wfds, &efds); - - assert(poll_fd->events != 0); - - fds = g_slist_remove(fds, poll_fd); - - if (events != poll_fd->events) - g_source_remove_poll(curl.source, poll_fd); - - if (events != 0) { - if (events != poll_fd->events) { - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - } - - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } else { - g_free(poll_fd); - } - } - - for (int fd = 0; fd <= max_fd; ++fd) { - gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); - if (events != 0) { - GPollFD *poll_fd = g_new(GPollFD, 1); - poll_fd->fd = fd; - poll_fd->events = events; - g_source_add_poll(curl.source, poll_fd); - curl.fds = g_slist_prepend(curl.fds, poll_fd); - } - } -} - -/** - * Runs in the I/O thread. No lock needed. - */ -static bool -input_curl_easy_add(struct input_curl *c, GError **error_r) -{ - assert(io_thread_inside()); - assert(c != NULL); - assert(c->easy != NULL); - assert(input_curl_find_request(c->easy) == NULL); - - curl.requests = g_slist_prepend(curl.requests, c); - - CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - curl_update_fds(); - - return true; -} - -struct easy_add_params { - struct input_curl *c; - GError **error_r; -}; - -static gpointer -input_curl_easy_add_callback(gpointer data) -{ - const struct easy_add_params *params = data; - - bool success = input_curl_easy_add(params->c, params->error_r); - return GUINT_TO_POINTER(success); -} - -/** - * Call input_curl_easy_add() in the I/O thread. May be called from - * any thread. Caller must not hold a mutex. - */ -static bool -input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) -{ - assert(c != NULL); - assert(c->easy != NULL); - - struct easy_add_params params = { - .c = c, - .error_r = error_r, - }; - - gpointer result = - io_thread_call(input_curl_easy_add_callback, ¶ms); - return GPOINTER_TO_UINT(result); -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * Runs in the I/O thread. - */ -static void -input_curl_easy_free(struct input_curl *c) -{ - assert(io_thread_inside()); - assert(c != NULL); - - if (c->easy == NULL) - return; - - curl.requests = g_slist_remove(curl.requests, c); - - curl_multi_remove_handle(curl.multi, c->easy); - curl_easy_cleanup(c->easy); - c->easy = NULL; - - curl_slist_free_all(c->request_headers); - c->request_headers = NULL; - - g_free(c->range); - c->range = NULL; -} - -static gpointer -input_curl_easy_free_callback(gpointer data) -{ - struct input_curl *c = data; - - input_curl_easy_free(c); - curl_update_fds(); - - return NULL; -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * The mutex must not be locked. - */ -static void -input_curl_easy_free_indirect(struct input_curl *c) -{ - io_thread_call(input_curl_easy_free_callback, c); - assert(c->easy == NULL); -} - -/** - * Abort and free all HTTP requests. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_abort_all_requests(GError *error) -{ - assert(io_thread_inside()); - assert(error != NULL); - - while (curl.requests != NULL) { - struct input_curl *c = curl.requests->data; - assert(c->postponed_error == NULL); - - input_curl_easy_free(c); - - g_mutex_lock(c->base.mutex); - c->postponed_error = g_error_copy(error); - c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - } - - g_error_free(error); - -} - -/** - * A HTTP request is finished. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_request_done(struct input_curl *c, CURLcode result, long status) -{ - assert(io_thread_inside()); - assert(c != NULL); - assert(c->easy == NULL); - assert(c->postponed_error == NULL); - - g_mutex_lock(c->base.mutex); - - if (result != CURLE_OK) { - c->postponed_error = g_error_new(curl_quark(), result, - "curl failed: %s", - c->error); - } else if (status < 200 || status >= 300) { - c->postponed_error = g_error_new(curl_quark(), 0, - "got HTTP status %ld", - status); - } - - c->base.ready = true; - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); -} - -static void -input_curl_handle_done(CURL *easy_handle, CURLcode result) -{ - struct input_curl *c = input_curl_find_request(easy_handle); - assert(c != NULL); - - long status = 0; - curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); - - input_curl_easy_free(c); - input_curl_request_done(c, result, status); -} - -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_info_read(void) -{ - assert(io_thread_inside()); - - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(curl.multi, - &msgs_in_queue)) != NULL) { - if (msg->msg == CURLMSG_DONE) - input_curl_handle_done(msg->easy_handle, msg->data.result); - } -} - -/** - * Give control to CURL. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static bool -input_curl_perform(void) -{ - assert(io_thread_inside()); - - CURLMcode mcode; - - do { - int running_handles; - mcode = curl_multi_perform(curl.multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM); - - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - GError *error = g_error_new(curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - input_curl_abort_all_requests(error); - return false; - } - - return true; -} - -/* - * GSource methods - * - */ - -/** - * The GSource prepare() method implementation. - */ -static gboolean -input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) -{ - curl_update_fds(); - -#if LIBCURL_VERSION_NUM >= 0x070f04 - curl.timeout = false; - - long timeout2; - CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); - if (mcode == CURLM_OK) { - if (timeout2 >= 0) - curl.absolute_timeout = g_source_get_time(source) - + timeout2 * 1000; - - if (timeout2 >= 0 && timeout2 < 10) - /* CURL 7.21.1 likes to report "timeout=0", - which means we're running in a busy loop. - Quite a bad idea to waste so much CPU. - Let's use a lower limit of 10ms. */ - timeout2 = 10; - - *timeout_r = timeout2; - - curl.timeout = timeout2 >= 0; - } else - g_warning("curl_multi_timeout() failed: %s\n", - curl_multi_strerror(mcode)); -#else - (void)timeout_r; -#endif - - return false; -} - -/** - * The GSource check() method implementation. - */ -static gboolean -input_curl_source_check(G_GNUC_UNUSED GSource *source) -{ -#if LIBCURL_VERSION_NUM >= 0x070f04 - if (curl.timeout) { - /* when a timeout has expired, we need to call - curl_multi_perform(), even if there was no file - descriptor event */ - - if (g_source_get_time(source) >= curl.absolute_timeout) - return true; - } -#endif - - for (GSList *i = curl.fds; i != NULL; i = i->next) { - GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; -} - -/** - * The GSource dispatch() method implementation. The callback isn't - * used, because we're handling all events directly. - */ -static gboolean -input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) -{ - if (input_curl_perform()) - input_curl_info_read(); - - return true; -} - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs curl_source_funcs = { - .prepare = input_curl_source_prepare, - .check = input_curl_source_check, - .dispatch = input_curl_source_dispatch, -}; - -/* - * input_plugin methods - * - */ - -static bool -input_curl_init(const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); - if (code != CURLE_OK) { - g_set_error(error_r, curl_quark(), code, - "curl_global_init() failed: %s\n", - curl_easy_strerror(code)); - return false; - } - - http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); - - proxy = config_get_block_string(param, "proxy", NULL); - proxy_port = config_get_block_unsigned(param, "proxy_port", 0); - proxy_user = config_get_block_string(param, "proxy_user", NULL); - proxy_password = config_get_block_string(param, "proxy_password", - NULL); - - if (proxy == NULL) { - /* deprecated proxy configuration */ - proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL); - proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); - proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL); - proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, - ""); - } - - curl.multi = curl_multi_init(); - if (curl.multi == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_multi_init() failed"); - return false; - } - - curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); - curl.source_id = g_source_attach(curl.source, io_thread_context()); - - return true; -} - -static gpointer -curl_destroy_sources(G_GNUC_UNUSED gpointer data) -{ - g_source_destroy(curl.source); - - return NULL; -} - -static void -input_curl_finish(void) -{ - assert(curl.requests == NULL); - - io_thread_call(curl_destroy_sources, NULL); - - curl_multi_cleanup(curl.multi); - - curl_slist_free_all(http_200_aliases); - - curl_global_cleanup(); -} - -#if LIBCURL_VERSION_NUM >= 0x071200 - -/** - * Determine the total sizes of all buffers, including portions that - * have already been consumed. - * - * The caller must lock the mutex. - */ -G_GNUC_PURE -static size_t -curl_total_buffer_size(const struct input_curl *c) -{ - size_t total = 0; - - for (GList *i = g_queue_peek_head_link(c->buffers); - i != NULL; i = g_list_next(i)) { - struct buffer *buffer = i->data; - total += buffer->size; - } - - return total; -} - -#endif - -static void -buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct buffer *buffer = data; - - assert(buffer->consumed <= buffer->size); - - g_free(buffer); -} - -static void -input_curl_flush_buffers(struct input_curl *c) -{ - g_queue_foreach(c->buffers, buffer_free_callback, NULL); - g_queue_clear(c->buffers); -} - -/** - * Frees this stream, including the input_stream struct. - */ -static void -input_curl_free(struct input_curl *c) -{ - if (c->tag != NULL) - tag_free(c->tag); - g_free(c->meta_name); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - g_queue_free(c->buffers); - - if (c->postponed_error != NULL) - g_error_free(c->postponed_error); - - g_free(c->url); - input_stream_deinit(&c->base); - g_free(c); -} - -static bool -input_curl_check(struct input_stream *is, GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - - bool success = c->postponed_error == NULL; - if (!success) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - } - - return success; -} - -static struct tag * -input_curl_tag(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - struct tag *tag = c->tag; - - c->tag = NULL; - return tag; -} - -static bool -fill_buffer(struct input_curl *c, GError **error_r) -{ - while (c->easy != NULL && g_queue_is_empty(c->buffers)) - g_cond_wait(c->base.cond, c->base.mutex); - - if (c->postponed_error != NULL) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - return false; - } - - return !g_queue_is_empty(c->buffers); -} - -/** - * Mark a part of the buffer object as consumed. - */ -static struct buffer * -consume_buffer(struct buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->consumed < buffer->size); - - buffer->consumed += length; - if (buffer->consumed < buffer->size) - return buffer; - - assert(buffer->consumed == buffer->size); - - g_free(buffer); - - return NULL; -} - -static size_t -read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers, - void *dest0, size_t length) -{ - struct buffer *buffer = g_queue_pop_head(buffers); - uint8_t *dest = dest0; - size_t nbytes = 0; - - assert(buffer->size > 0); - assert(buffer->consumed < buffer->size); - - if (length > buffer->size - buffer->consumed) - length = buffer->size - buffer->consumed; - - while (true) { - size_t chunk; - - chunk = icy_data(icy_metadata, length); - if (chunk > 0) { - memcpy(dest, buffer->data + buffer->consumed, - chunk); - buffer = consume_buffer(buffer, chunk); - - nbytes += chunk; - dest += chunk; - length -= chunk; - - if (length == 0) - break; - - assert(buffer != NULL); - } - - chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed, - length); - if (chunk > 0) { - buffer = consume_buffer(buffer, chunk); - - length -= chunk; - - if (length == 0) - break; - - assert(buffer != NULL); - } - } - - if (buffer != NULL) - g_queue_push_head(buffers, buffer); - - return nbytes; -} - -static void -copy_icy_tag(struct input_curl *c) -{ - struct tag *tag = icy_tag(&c->icy_metadata); - - if (tag == NULL) - return; - - if (c->tag != NULL) - tag_free(c->tag); - - if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) - tag_add_item(tag, TAG_NAME, c->meta_name); - - c->tag = tag; -} - -static bool -input_curl_available(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->postponed_error != NULL || c->easy == NULL || - !g_queue_is_empty(c->buffers); -} - -static size_t -input_curl_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - bool success; - size_t nbytes = 0; - char *dest = ptr; - - do { - /* fill the buffer */ - - success = fill_buffer(c, error_r); - if (!success) - return 0; - - /* send buffer contents */ - - while (size > 0 && !g_queue_is_empty(c->buffers)) { - size_t copy = read_from_buffer(&c->icy_metadata, c->buffers, - dest + nbytes, size); - - nbytes += copy; - size -= copy; - } - } while (nbytes == 0); - - if (icy_defined(&c->icy_metadata)) - copy_icy_tag(c); - - is->offset += (goffset)nbytes; - -#if LIBCURL_VERSION_NUM >= 0x071200 - if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { - g_mutex_unlock(c->base.mutex); - io_thread_call(input_curl_resume, c); - g_mutex_lock(c->base.mutex); - } -#endif - - return nbytes; -} - -static void -input_curl_close(struct input_stream *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 = (struct input_curl *)is; - - return c->easy == NULL && g_queue_is_empty(c->buffers); -} - -/** called by curl when new data is available */ -static size_t -input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - const char *header = ptr, *end, *value; - char name[64]; - - size *= nmemb; - end = header + size; - - value = memchr(header, ':', size); - if (value == NULL || (size_t)(value - header) >= sizeof(name)) - return size; - - memcpy(name, header, value - header); - name[value - header] = 0; - - /* skip the colon */ - - ++value; - - /* strip the value */ - - while (value < end && g_ascii_isspace(*value)) - ++value; - - while (end > value && g_ascii_isspace(end[-1])) - --end; - - if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { - /* a stream with icy-metadata is not seekable */ - if (!icy_defined(&c->icy_metadata)) - c->base.seekable = true; - } else if (g_ascii_strcasecmp(name, "content-length") == 0) { - char buffer[64]; - - if ((size_t)(end - header) >= sizeof(buffer)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); - } else if (g_ascii_strcasecmp(name, "content-type") == 0) { - 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) { - g_free(c->meta_name); - c->meta_name = g_strndup(value, end - value); - - if (c->tag != NULL) - tag_free(c->tag); - - c->tag = tag_new(); - 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; - - if ((size_t)(end - header) >= sizeof(buffer) || - icy_defined(&c->icy_metadata)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - icy_metaint = g_ascii_strtoull(buffer, NULL, 10); - g_debug("icy-metaint=%zu", icy_metaint); - - if (icy_metaint > 0) { - icy_start(&c->icy_metadata, icy_metaint); - - /* a stream with icy-metadata is not - seekable */ - c->base.seekable = false; - } - } - - return size; -} - -/** called by curl when new data is available */ -static size_t -input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - struct buffer *buffer; - - size *= nmemb; - if (size == 0) - return 0; - - g_mutex_lock(c->base.mutex); - -#if LIBCURL_VERSION_NUM >= 0x071200 - if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { - c->paused = true; - g_mutex_unlock(c->base.mutex); - return CURL_WRITEFUNC_PAUSE; - } -#endif - - buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - buffer->size = size; - buffer->consumed = 0; - memcpy(buffer->data, ptr, size); - - g_queue_push_tail(c->buffers, buffer); - c->base.ready = true; - - g_cond_broadcast(c->base.cond); - g_mutex_unlock(c->base.mutex); - - return size; -} - -static bool -input_curl_easy_init(struct input_curl *c, GError **error_r) -{ - CURLcode code; - - c->easy = curl_easy_init(); - if (c->easy == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_easy_init() failed"); - 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, c); - curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, - input_curl_writefunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); - curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); - curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); - curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); - curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); - curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); - curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); - curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); - curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); - - if (proxy != NULL) - curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); - - if (proxy_port > 0) - curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); - - if (proxy_user != NULL && proxy_password != NULL) { - char *proxy_auth_str = - g_strconcat(proxy_user, ":", proxy_password, NULL); - curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); - g_free(proxy_auth_str); - } - - code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); - 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, - "Icy-Metadata: 1"); - curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); - - return true; -} - -static bool -input_curl_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - bool ret; - - assert(is->ready); - - if (whence == SEEK_SET && offset == is->offset) - /* no-op */ - return true; - - if (!is->seekable) - return false; - - /* calculate the absolute offset */ - - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - offset += is->offset; - break; - - case SEEK_END: - if (is->size < 0) - /* stream size is not known */ - return false; - - offset += is->size; - break; - - default: - return false; - } - - if (offset < 0) - return false; - - /* check if we can fast-forward the buffer */ - - while (offset > is->offset && !g_queue_is_empty(c->buffers)) { - struct buffer *buffer; - size_t length; - - buffer = (struct buffer *)g_queue_pop_head(c->buffers); - - length = buffer->size - buffer->consumed; - if (offset - is->offset < (goffset)length) - length = offset - is->offset; - - buffer = consume_buffer(buffer, length); - if (buffer != NULL) - g_queue_push_head(c->buffers, buffer); - - is->offset += length; - } - - if (offset == is->offset) - return true; - - /* close the old connection and open a new one */ - - g_mutex_unlock(c->base.mutex); - - input_curl_easy_free_indirect(c); - input_curl_flush_buffers(c); - - is->offset = offset; - if (is->offset == is->size) { - /* seek to EOF: simulate empty result; avoid - triggering a "416 Requested Range Not Satisfiable" - response */ - return true; - } - - ret = input_curl_easy_init(c, error_r); - if (!ret) - return false; - - /* send the "Range" header */ - - if (is->offset > 0) { - c->range = g_strdup_printf("%lld-", (long long)is->offset); - curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); - } - - c->base.ready = false; - - if (!input_curl_easy_add_indirect(c, error_r)) - return false; - - g_mutex_lock(c->base.mutex); - - while (!c->base.ready) - g_cond_wait(c->base.cond, c->base.mutex); - - if (c->postponed_error != NULL) { - g_propagate_error(error_r, c->postponed_error); - c->postponed_error = NULL; - return false; - } - - return true; -} - -static struct input_stream * -input_curl_open(const char *url, GMutex *mutex, GCond *cond, - GError **error_r) -{ - assert(mutex != NULL); - assert(cond != NULL); - - struct input_curl *c; - - if (strncmp(url, "http://", 7) != 0) - return NULL; - - c = g_new0(struct input_curl, 1); - input_stream_init(&c->base, &input_plugin_curl, url, - mutex, cond); - - c->url = g_strdup(url); - c->buffers = g_queue_new(); - - icy_clear(&c->icy_metadata); - c->tag = NULL; - - c->postponed_error = NULL; - -#if LIBCURL_VERSION_NUM >= 0x071200 - c->paused = false; -#endif - - if (!input_curl_easy_init(c, error_r)) { - input_curl_free(c); - return NULL; - } - - if (!input_curl_easy_add_indirect(c, error_r)) { - input_curl_free(c); - return NULL; - } - - return &c->base; -} - -const struct input_plugin input_plugin_curl = { - .name = "curl", - .init = input_curl_init, - .finish = input_curl_finish, - - .open = input_curl_open, - .close = input_curl_close, - .check = input_curl_check, - .tag = input_curl_tag, - .available = input_curl_available, - .read = input_curl_read, - .eof = input_curl_eof, - .seek = input_curl_seek, -}; diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h deleted file mode 100644 index c6e71bf40..000000000 --- a/src/input/curl_input_plugin.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_CURL_H -#define MPD_INPUT_CURL_H - -struct input_stream; - -extern const struct input_plugin input_plugin_curl; - -#endif diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c deleted file mode 100644 index 200a0afd6..000000000 --- a/src/input/despotify_input_plugin.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/despotify_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "tag.h" -#include "despotify_utils.h" - -#include <glib.h> - -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <despotify.h> - -#include <stdio.h> - -struct input_despotify { - struct input_stream base; - - struct despotify_session *session; - struct ds_track *track; - struct tag *tag; - struct ds_pcm_data pcm; - size_t len_available; - bool eof; -}; - - -static void -refill_buffer(struct input_despotify *ctx) -{ - /* Wait until there is data */ - while (1) { - int rc = despotify_get_pcm(ctx->session, &ctx->pcm); - - if (rc == 0 && ctx->pcm.len) { - ctx->len_available = ctx->pcm.len; - break; - } - if (ctx->eof == true) - break; - - if (rc < 0) { - g_debug("despotify_get_pcm error\n"); - ctx->eof = true; - break; - } - - /* Wait a while until next iteration */ - usleep(50 * 1000); - } -} - -static void callback(G_GNUC_UNUSED struct despotify_session* ds, - int sig, G_GNUC_UNUSED void* data, void* callback_data) -{ - struct input_despotify *ctx = (struct input_despotify *)callback_data; - - switch (sig) { - case DESPOTIFY_NEW_TRACK: - break; - - case DESPOTIFY_TIME_TELL: - break; - - case DESPOTIFY_TRACK_PLAY_ERROR: - g_debug("Track play error\n"); - ctx->eof = true; - ctx->len_available = 0; - break; - - case DESPOTIFY_END_OF_PLAYLIST: - ctx->eof = true; - g_debug("End of playlist: %d\n", ctx->eof); - break; - } -} - - -static struct input_stream * -input_despotify_open(const char *url, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) -{ - struct input_despotify *ctx; - struct despotify_session *session; - struct ds_link *ds_link; - struct ds_track *track; - - if (!g_str_has_prefix(url, "spt://")) - return NULL; - - session = mpd_despotify_get_session(); - if (!session) - return NULL; - - ds_link = despotify_link_from_uri(url + 6); - if (!ds_link) { - g_debug("Can't find %s\n", url); - return NULL; - } - if (ds_link->type != LINK_TYPE_TRACK) { - despotify_free_link(ds_link); - return NULL; - } - - ctx = g_new(struct input_despotify, 1); - memset(ctx, 0, sizeof(*ctx)); - - track = despotify_link_get_track(session, ds_link); - despotify_free_link(ds_link); - if (!track) { - g_free(ctx); - return NULL; - } - - input_stream_init(&ctx->base, &input_plugin_despotify, url, - mutex, cond); - ctx->session = session; - ctx->track = track; - ctx->tag = mpd_despotify_tag_from_track(track); - ctx->eof = false; - /* Despotify outputs pcm data */ - ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); - ctx->base.ready = true; - - if (!mpd_despotify_register_callback(callback, ctx)) { - despotify_free_link(ds_link); - - return NULL; - } - - if (despotify_play(ctx->session, ctx->track, false) == false) { - despotify_free_track(ctx->track); - g_free(ctx); - return NULL; - } - - return &ctx->base; -} - -static size_t -input_despotify_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - size_t to_cpy = size; - - if (ctx->len_available == 0) - refill_buffer(ctx); - - if (ctx->len_available < size) - to_cpy = ctx->len_available; - memcpy(ptr, ctx->pcm.buf, to_cpy); - ctx->len_available -= to_cpy; - - is->offset += to_cpy; - - return to_cpy; -} - -static void -input_despotify_close(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - - if (ctx->tag != NULL) - tag_free(ctx->tag); - - mpd_despotify_unregister_callback(callback); - despotify_free_track(ctx->track); - input_stream_deinit(&ctx->base); - g_free(ctx); -} - -static bool -input_despotify_eof(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - - return ctx->eof; -} - -static bool -input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -static struct tag * -input_despotify_tag(struct input_stream *is) -{ - struct input_despotify *ctx = (struct input_despotify *)is; - struct tag *tag = ctx->tag; - - ctx->tag = NULL; - - return tag; -} - -const struct input_plugin input_plugin_despotify = { - .name = "spt", - .open = input_despotify_open, - .close = input_despotify_close, - .read = input_despotify_read, - .eof = input_despotify_eof, - .seek = input_despotify_seek, - .tag = input_despotify_tag, -}; diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h deleted file mode 100644 index 4c070d882..000000000 --- a/src/input/despotify_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef INPUT_DESPOTIFY_H -#define INPUT_DESPOTIFY_H - -extern const struct input_plugin input_plugin_despotify; - -#endif diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c deleted file mode 100644 index 6d339a067..000000000 --- a/src/input/ffmpeg_input_plugin.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/ffmpeg_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" - -#include <libavutil/avutil.h> -#include <libavformat/avio.h> -#include <libavformat/avformat.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_ffmpeg" - -struct input_ffmpeg { - struct input_stream base; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - AVIOContext *h; -#else - URLContext *h; -#endif - - bool eof; -}; - -static inline GQuark -ffmpeg_quark(void) -{ - return g_quark_from_static_string("ffmpeg"); -} - -static inline bool -input_ffmpeg_supported(void) -{ -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - void *opaque = NULL; - return avio_enum_protocols(&opaque, 0) != NULL; -#else - return av_protocol_next(NULL) != NULL; -#endif -} - -static bool -input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - av_register_all(); - - /* disable this plugin if there's no registered protocol */ - if (!input_ffmpeg_supported()) { - g_set_error(error_r, ffmpeg_quark(), 0, - "No protocol"); - return false; - } - - return true; -} - -static struct input_stream * -input_ffmpeg_open(const char *uri, - GMutex *mutex, GCond *cond, - 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, - mutex, cond); - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0) - int ret = avio_open(&i->h, uri, AVIO_FLAG_READ); -#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_open(&i->h, uri, AVIO_RDONLY); -#else - int ret = url_open(&i->h, uri, URL_RDONLY); -#endif - 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; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0; - i->base.size = avio_size(i->h); -#else - i->base.seekable = !i->h->is_streamed; - i->base.size = url_filesize(i->h); -#endif - - /* 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; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int ret = avio_read(i->h, ptr, size); -#else - int ret = url_read(i->h, ptr, size); -#endif - 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; - -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - avio_close(i->h); -#else - url_close(i->h); -#endif - 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; -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) - int64_t ret = avio_seek(i->h, offset, whence); -#else - int64_t ret = url_seek(i->h, offset, whence); -#endif - - 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 deleted file mode 100644 index 393836ca5..000000000 --- a/src/input/ffmpeg_input_plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index 5ee3f200b..000000000 --- a/src/input/file_input_plugin.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "input/file_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "fd_util.h" -#include "open.h" - -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_file" - -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, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - int fd, ret; - struct stat st; - struct file_input_stream *fis; - - if (!g_path_is_absolute(filename)) - return NULL; - - fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); - if (fd < 0) { - if (errno != ENOENT && errno != ENOTDIR) - g_set_error(error_r, file_quark(), errno, - "Failed to open \"%s\": %s", - filename, g_strerror(errno)); - return NULL; - } - - ret = fstat(fd, &st); - if (ret < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to stat \"%s\": %s", - filename, g_strerror(errno)); - close(fd); - return NULL; - } - - if (!S_ISREG(st.st_mode)) { - g_set_error(error_r, file_quark(), 0, - "Not a regular file: %s", filename); - close(fd); - return NULL; - } - -#ifdef POSIX_FADV_SEQUENTIAL - posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); -#endif - - fis = g_new(struct file_input_stream, 1); - input_stream_init(&fis->base, &input_plugin_file, filename, - mutex, cond); - - 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, goffset offset, int whence, - GError **error_r) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - - offset = (goffset)lseek(fis->fd, (off_t)offset, whence); - if (offset < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to seek: %s", g_strerror(errno)); - return false; - } - - is->offset = offset; - return true; -} - -static size_t -input_file_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - ssize_t nbytes; - - nbytes = read(fis->fd, ptr, size); - if (nbytes < 0) { - g_set_error(error_r, file_quark(), errno, - "Failed to read: %s", g_strerror(errno)); - return 0; - } - - is->offset += nbytes; - return (size_t)nbytes; -} - -static void -input_file_close(struct input_stream *is) -{ - struct file_input_stream *fis = (struct file_input_stream *)is; - - close(fis->fd); - input_stream_deinit(&fis->base); - g_free(fis); -} - -static bool -input_file_eof(struct input_stream *is) -{ - return is->offset >= is->size; -} - -const struct input_plugin input_plugin_file = { - .name = "file", - .open = input_file_open, - .close = input_file_close, - .read = input_file_read, - .eof = input_file_eof, - .seek = input_file_seek, -}; diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h deleted file mode 100644 index f24769d57..000000000 --- a/src/input/file_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_FILE_H -#define MPD_INPUT_FILE_H - -extern const struct input_plugin input_plugin_file; - -#endif diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c deleted file mode 100644 index cff15125b..000000000 --- a/src/input/mms_input_plugin.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/mms_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" - -#include <glib.h> -#include <libmms/mmsx.h> - -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_mms" - -struct input_mms { - struct input_stream base; - - mmsx_t *mms; - - bool eof; -}; - -static inline GQuark -mms_quark(void) -{ - return g_quark_from_static_string("mms"); -} - -static struct input_stream * -input_mms_open(const char *url, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - struct input_mms *m; - - if (!g_str_has_prefix(url, "mms://") && - !g_str_has_prefix(url, "mmsh://") && - !g_str_has_prefix(url, "mmst://") && - !g_str_has_prefix(url, "mmsu://")) - return NULL; - - m = g_new(struct input_mms, 1); - input_stream_init(&m->base, &input_plugin_mms, url, - mutex, cond); - - m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); - if (m->mms == NULL) { - g_free(m); - 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*/ - m->base.mime = g_strdup("audio/x-ms-wma"); - - m->base.ready = true; - - return &m->base; -} - -static size_t -input_mms_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_mms *m = (struct input_mms *)is; - int ret; - - ret = mmsx_read(NULL, m->mms, ptr, size); - if (ret <= 0) { - if (ret < 0) { - g_set_error(error_r, mms_quark(), errno, - "mmsx_read() failed: %s", - g_strerror(errno)); - } - - m->eof = true; - return false; - } - - is->offset += ret; - - return (size_t)ret; -} - -static void -input_mms_close(struct input_stream *is) -{ - 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 = (struct input_mms *)is; - - return m->eof; -} - -static bool -input_mms_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -const struct input_plugin input_plugin_mms = { - .name = "mms", - .open = input_mms_open, - .close = input_mms_close, - .read = input_mms_read, - .eof = input_mms_eof, - .seek = input_mms_seek, -}; diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c deleted file mode 100644 index cf06fc57b..000000000 --- a/src/input/rewind_input_plugin.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/rewind_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_rewind" - -struct input_rewind { - struct input_stream base; - - struct input_stream *input; - - /** - * The read position within the buffer. Undefined as long as - * reading_from_buffer() returns false. - */ - size_t head; - - /** - * The write/append position within the buffer. - */ - size_t tail; - - /** - * The size of this buffer is the maximum number of bytes - * which can be rewinded cheaply without passing the "seek" - * call to CURL. - * - * The origin of this buffer is always the beginning of the - * stream (offset 0). - */ - char buffer[64 * 1024]; -}; - -/** - * Are we currently reading from the buffer, and does the buffer - * contain more data for the next read operation? - */ -static bool -reading_from_buffer(const struct input_rewind *r) -{ - return r->tail > 0 && r->base.offset < r->input->offset; -} - -/** - * Copy public attributes from the underlying input stream to the - * "rewind" input stream. This function is called when a method of - * the underlying stream has returned, which may have modified these - * attributes. - */ -static void -copy_attributes(struct input_rewind *r) -{ - struct input_stream *dest = &r->base; - const struct input_stream *src = r->input; - - assert(dest != src); - assert(src->mime == NULL || dest->mime != src->mime); - - bool dest_ready = dest->ready; - - dest->ready = src->ready; - dest->seekable = src->seekable; - dest->size = src->size; - dest->offset = src->offset; - - if (!dest_ready && src->ready) { - g_free(dest->mime); - dest->mime = g_strdup(src->mime); - } -} - -static void -input_rewind_close(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - input_stream_close(r->input); - - input_stream_deinit(&r->base); - g_free(r); -} - -static bool -input_rewind_check(struct input_stream *is, GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_check(r->input, error_r); -} - -static void -input_rewind_update(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - if (!reading_from_buffer(r)) - copy_attributes(r); -} - -static struct tag * -input_rewind_tag(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_tag(r->input); -} - -static bool -input_rewind_available(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return input_stream_available(r->input); -} - -static size_t -input_rewind_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - if (reading_from_buffer(r)) { - /* buffered read */ - - assert(r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - if (size > r->tail - r->head) - size = r->tail - r->head; - - memcpy(ptr, r->buffer + r->head, size); - r->head += size; - is->offset += size; - - return size; - } else { - /* pass method call to underlying stream */ - - size_t nbytes = input_stream_read(r->input, ptr, size, error_r); - - if (r->input->offset > (goffset)sizeof(r->buffer)) - /* disable buffering */ - r->tail = 0; - else if (r->tail == (size_t)is->offset) { - /* append to buffer */ - - memcpy(r->buffer + r->tail, ptr, nbytes); - r->tail += nbytes; - - assert(r->tail == (size_t)r->input->offset); - } - - copy_attributes(r); - - return nbytes; - } -} - -static bool -input_rewind_eof(struct input_stream *is) -{ - struct input_rewind *r = (struct input_rewind *)is; - - return !reading_from_buffer(r) && input_stream_eof(r->input); -} - -static bool -input_rewind_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - struct input_rewind *r = (struct input_rewind *)is; - - assert(is->ready); - - if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { - /* buffered seek */ - - assert(!reading_from_buffer(r) || - r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - r->head = (size_t)offset; - is->offset = offset; - - return true; - } else { - bool success = input_stream_seek(r->input, offset, whence, - error_r); - copy_attributes(r); - - /* disable the buffer, because r->input has left the - buffered range now */ - r->tail = 0; - - return success; - } -} - -static const struct input_plugin rewind_input_plugin = { - .close = input_rewind_close, - .check = input_rewind_check, - .update = input_rewind_update, - .tag = input_rewind_tag, - .available = input_rewind_available, - .read = input_rewind_read, - .eof = input_rewind_eof, - .seek = input_rewind_seek, -}; - -struct input_stream * -input_rewind_open(struct input_stream *is) -{ - struct input_rewind *c; - - assert(is != NULL); - assert(is->offset == 0); - - 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, - is->mutex, is->cond); - c->tail = 0; - c->input = is; - - return &c->base; -} diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h deleted file mode 100644 index 83abe257a..000000000 --- a/src/input/rewind_input_plugin.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A wrapper for an input_stream object which allows cheap buffered - * rewinding. This is useful while detecting the stream codec (let - * each decoder plugin peek a portion from the stream). - */ - -#ifndef MPD_INPUT_REWIND_H -#define MPD_INPUT_REWIND_H - -#include "check.h" - -struct input_stream; - -struct input_stream * -input_rewind_open(struct input_stream *is); - -#endif diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c deleted file mode 100644 index fc903b48c..000000000 --- a/src/input/soup_input_plugin.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input/soup_input_plugin.h" -#include "input_internal.h" -#include "input_plugin.h" -#include "io_thread.h" -#include "conf.h" - -#include <libsoup/soup-uri.h> -#include <libsoup/soup-session-async.h> - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_soup" - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t SOUP_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t SOUP_RESUME_AT = 384 * 1024; - -static SoupURI *soup_proxy; -static SoupSession *soup_session; - -struct input_soup { - struct input_stream base; - - SoupMessage *msg; - - GQueue *buffers; - - size_t current_consumed; - - size_t total_buffered; - - bool alive, pause, eof; - - /** - * Set when the session callback has been invoked, when it is - * safe to free this object. - */ - bool completed; - - GError *postponed_error; -}; - -static inline GQuark -soup_quark(void) -{ - return g_quark_from_static_string("soup"); -} - -static bool -input_soup_init(const struct config_param *param, GError **error_r) -{ - assert(soup_proxy == NULL); - assert(soup_session == NULL); - - g_type_init(); - - const char *proxy = config_get_block_string(param, "proxy", NULL); - - if (proxy != NULL) { - soup_proxy = soup_uri_new(proxy); - if (soup_proxy == NULL) { - g_set_error(error_r, soup_quark(), 0, - "failed to parse proxy setting"); - return false; - } - } - - soup_session = - soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, - soup_proxy, - SOUP_SESSION_ASYNC_CONTEXT, - io_thread_context(), - NULL); - - return true; -} - -static void -input_soup_finish(void) -{ - assert(soup_session != NULL); - - soup_session_abort(soup_session); - g_object_unref(G_OBJECT(soup_session)); - - if (soup_proxy != NULL) - soup_uri_free(soup_proxy); -} - -/** - * Copy the error from the SoupMessage object to - * input_soup::postponed_error. - * - * @return true if there was no error - */ -static bool -input_soup_copy_error(struct input_soup *s, const SoupMessage *msg) -{ - if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) - return true; - - if (msg->status_code == SOUP_STATUS_CANCELLED) - /* failure, but don't generate a GError, because this - status was caused by _close() */ - return false; - - if (s->postponed_error != NULL) - /* there's already a GError, don't overwrite it */ - return false; - - if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, - "HTTP client error: %s", - msg->reason_phrase); - else - s->postponed_error = - g_error_new(soup_quark(), msg->status_code, - "got HTTP status: %d %s", - msg->status_code, msg->reason_phrase); - - return false; -} - -static void -input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, - SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - assert(!s->completed); - - g_mutex_lock(s->base.mutex); - - if (!s->base.ready) - input_soup_copy_error(s, msg); - - s->base.ready = true; - s->alive = false; - s->completed = true; - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static void -input_soup_got_headers(SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - g_mutex_lock(s->base.mutex); - - if (!input_soup_copy_error(s, msg)) { - g_mutex_unlock(s->base.mutex); - - soup_session_cancel_message(soup_session, msg, - SOUP_STATUS_CANCELLED); - return; - } - - s->base.ready = true; - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); - - soup_message_body_set_accumulate(msg->response_body, false); -} - -static void -input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - - g_mutex_lock(s->base.mutex); - - g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); - s->total_buffered += chunk->length; - - if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { - s->pause = true; - soup_session_pause_message(soup_session, msg); - } - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static void -input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) -{ - struct input_soup *s = user_data; - - assert(msg == s->msg); - - g_mutex_lock(s->base.mutex); - - s->base.ready = true; - s->eof = true; - s->alive = false; - - g_cond_broadcast(s->base.cond); - g_mutex_unlock(s->base.mutex); -} - -static bool -input_soup_wait_data(struct input_soup *s) -{ - while (true) { - if (s->eof) - return true; - - if (!s->alive) - return false; - - if (!g_queue_is_empty(s->buffers)) - return true; - - assert(s->current_consumed == 0); - - g_cond_wait(s->base.cond, s->base.mutex); - } -} - -static gpointer -input_soup_queue(gpointer data) -{ - struct input_soup *s = data; - - soup_session_queue_message(soup_session, s->msg, - input_soup_session_callback, s); - - return NULL; -} - -static struct input_stream * -input_soup_open(const char *uri, - GMutex *mutex, GCond *cond, - G_GNUC_UNUSED GError **error_r) -{ - if (strncmp(uri, "http://", 7) != 0) - return NULL; - - struct input_soup *s = g_new(struct input_soup, 1); - input_stream_init(&s->base, &input_plugin_soup, uri, - mutex, cond); - - s->buffers = g_queue_new(); - s->current_consumed = 0; - s->total_buffered = 0; - -#if GCC_CHECK_VERSION(4,6) -#pragma GCC diagnostic push - /* the libsoup macro SOUP_METHOD_GET discards the "const" - attribute of the g_intern_static_string() return value; - don't make the gcc warning fatal: */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - - s->msg = soup_message_new(SOUP_METHOD_GET, uri); - -#if GCC_CHECK_VERSION(4,6) -#pragma GCC diagnostic pop -#endif - - soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT); - - soup_message_headers_append(s->msg->request_headers, "User-Agent", - "Music Player Daemon " VERSION); - - g_signal_connect(s->msg, "got-headers", - G_CALLBACK(input_soup_got_headers), s); - g_signal_connect(s->msg, "got-chunk", - G_CALLBACK(input_soup_got_chunk), s); - g_signal_connect(s->msg, "got-body", - G_CALLBACK(input_soup_got_body), s); - - s->alive = true; - s->pause = false; - s->eof = false; - s->completed = false; - s->postponed_error = NULL; - - io_thread_call(input_soup_queue, s); - - return &s->base; -} - -static gpointer -input_soup_cancel(gpointer data) -{ - struct input_soup *s = data; - - if (!s->completed) - soup_session_cancel_message(soup_session, s->msg, - SOUP_STATUS_CANCELLED); - - return NULL; -} - -static void -input_soup_close(struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - g_mutex_lock(s->base.mutex); - - if (!s->completed) { - /* the messages's session callback hasn't been invoked - yet; cancel it and wait for completion */ - - g_mutex_unlock(s->base.mutex); - - io_thread_call(input_soup_cancel, s); - - g_mutex_lock(s->base.mutex); - while (!s->completed) - g_cond_wait(s->base.cond, s->base.mutex); - } - - g_mutex_unlock(s->base.mutex); - - SoupBuffer *buffer; - while ((buffer = g_queue_pop_head(s->buffers)) != NULL) - soup_buffer_free(buffer); - g_queue_free(s->buffers); - - if (s->postponed_error != NULL) - g_error_free(s->postponed_error); - - input_stream_deinit(&s->base); - g_free(s); -} - -static bool -input_soup_check(struct input_stream *is, GError **error_r) -{ - struct input_soup *s = (struct input_soup *)is; - - bool success = s->postponed_error == NULL; - if (!success) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; - } - - return success; -} - -static bool -input_soup_available(struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - return s->eof || !s->alive || !g_queue_is_empty(s->buffers); -} - -static size_t -input_soup_read(struct input_stream *is, void *ptr, size_t size, - G_GNUC_UNUSED GError **error_r) -{ - struct input_soup *s = (struct input_soup *)is; - - if (!input_soup_wait_data(s)) { - assert(!s->alive); - - if (s->postponed_error != NULL) { - g_propagate_error(error_r, s->postponed_error); - s->postponed_error = NULL; - } else - g_set_error_literal(error_r, soup_quark(), 0, - "HTTP failure"); - return 0; - } - - char *p0 = ptr, *p = p0, *p_end = p0 + size; - - while (p < p_end) { - SoupBuffer *buffer = g_queue_pop_head(s->buffers); - if (buffer == NULL) { - assert(s->current_consumed == 0); - break; - } - - assert(s->current_consumed < buffer->length); - assert(s->total_buffered >= buffer->length); - - const char *q = buffer->data; - q += s->current_consumed; - - size_t remaining = buffer->length - s->current_consumed; - size_t nbytes = p_end - p; - if (nbytes > remaining) - nbytes = remaining; - - memcpy(p, q, nbytes); - p += nbytes; - - s->current_consumed += remaining; - if (s->current_consumed >= buffer->length) { - /* done with this buffer */ - s->total_buffered -= buffer->length; - soup_buffer_free(buffer); - s->current_consumed = 0; - } else { - /* partial read */ - assert(p == p_end); - - g_queue_push_head(s->buffers, buffer); - } - } - - if (s->pause && s->total_buffered < SOUP_RESUME_AT) { - s->pause = false; - soup_session_unpause_message(soup_session, s->msg); - } - - size_t nbytes = p - p0; - s->base.offset += nbytes; - - return nbytes; -} - -static bool -input_soup_eof(G_GNUC_UNUSED struct input_stream *is) -{ - struct input_soup *s = (struct input_soup *)is; - - return !s->alive && g_queue_is_empty(s->buffers); -} - -const struct input_plugin input_plugin_soup = { - .name = "soup", - .init = input_soup_init, - .finish = input_soup_finish, - - .open = input_soup_open, - .close = input_soup_close, - .check = input_soup_check, - .available = input_soup_available, - .read = input_soup_read, - .eof = input_soup_eof, -}; diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h deleted file mode 100644 index 689b2d971..000000000 --- a/src/input/soup_input_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_SOUP_H -#define MPD_INPUT_SOUP_H - -extern const struct input_plugin input_plugin_soup; - -#endif diff --git a/src/input_init.c b/src/input_init.c deleted file mode 100644 index 771d648d1..000000000 --- a/src/input_init.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_init.h" -#include "input_plugin.h" -#include "input_registry.h" -#include "conf.h" - -#include <assert.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]; - - assert(plugin->name != NULL); - assert(*plugin->name != 0); - assert(plugin->open != NULL); - - const struct config_param *param = - input_plugin_config(plugin->name, &error); - if (param == NULL && error != NULL) { - 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) -{ - input_plugins_for_each_enabled(plugin) - if (plugin->finish != NULL) - plugin->finish(); -} diff --git a/src/input_init.h b/src/input_init.h deleted file mode 100644 index ad92cda08..000000000 --- a/src/input_init.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_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 occurring, 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_internal.c b/src/input_internal.c deleted file mode 100644 index 92a71856e..000000000 --- a/src/input_internal.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_internal.h" -#include "input_stream.h" - -#include <assert.h> - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond) -{ - assert(is != NULL); - assert(plugin != NULL); - assert(uri != NULL); - - is->plugin = plugin; - is->uri = g_strdup(uri); - is->mutex = mutex; - is->cond = cond; - is->ready = false; - is->seekable = false; - is->size = -1; - is->offset = 0; - is->mime = NULL; -} - -void -input_stream_deinit(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - g_free(is->uri); - g_free(is->mime); -} - -void -input_stream_signal_client(struct input_stream *is) -{ - if (is->cond != NULL) - g_cond_broadcast(is->cond); -} - -void -input_stream_set_ready(struct input_stream *is) -{ - g_mutex_lock(is->mutex); - - if (!is->ready) { - is->ready = true; - input_stream_signal_client(is); - } - - g_mutex_unlock(is->mutex); -} diff --git a/src/input_internal.h b/src/input_internal.h deleted file mode 100644 index d95142e46..000000000 --- a/src/input_internal.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_INTERNAL_H -#define MPD_INPUT_INTERNAL_H - -#include "check.h" - -#include <glib.h> - -struct input_stream; -struct input_plugin; - -void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri, GMutex *mutex, GCond *cond); - -void -input_stream_deinit(struct input_stream *is); - -void -input_stream_signal_client(struct input_stream *is); - -void -input_stream_set_ready(struct input_stream *is); - -#endif diff --git a/src/input_plugin.h b/src/input_plugin.h deleted file mode 100644 index 6b0c77c85..000000000 --- a/src/input_plugin.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_PLUGIN_H -#define MPD_INPUT_PLUGIN_H - -#include "input_stream.h" - -#include <stddef.h> -#include <stdbool.h> -#include <sys/types.h> - -struct config_param; -struct input_stream; - -struct input_plugin { - const char *name; - - /** - * Global initialization. This method is called when MPD starts. - * - * @param error_r location to store the error occurring, or - * NULL to ignore errors - * @return true on success, false if the plugin should be - * disabled - */ - bool (*init)(const struct config_param *param, GError **error_r); - - /** - * Global deinitialization. Called once before MPD shuts - * down (only if init() has returned true). - */ - void (*finish)(void); - - struct input_stream *(*open)(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r); - void (*close)(struct input_stream *is); - - /** - * Check for errors that may have occurred in the I/O thread. - * May be unimplemented for synchronous plugins. - * - * @return false on error - */ - bool (*check)(struct input_stream *is, GError **error_r); - - /** - * Update the public attributes. Call before access. Can be - * NULL if the plugin always keeps its attributes up to date. - */ - void (*update)(struct input_stream *is); - - struct tag *(*tag)(struct input_stream *is); - - /** - * Returns true if the next read operation will not block: - * either data is available, or end-of-stream has been - * reached, or an error has occurred. - * - * If this method is unimplemented, then it is assumed that - * reading will never block. - */ - bool (*available)(struct input_stream *is); - - size_t (*read)(struct input_stream *is, void *ptr, size_t size, - GError **error_r); - bool (*eof)(struct input_stream *is); - 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 deleted file mode 100644 index 5987d5da2..000000000 --- a/src/input_registry.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_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 ENABLE_SOUP -#include "input/soup_input_plugin.h" -#endif - -#ifdef HAVE_FFMPEG -#include "input/ffmpeg_input_plugin.h" -#endif - -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif - -#ifdef ENABLE_CDIO_PARANOIA -#include "input/cdio_paranoia_input_plugin.h" -#endif - -#ifdef ENABLE_DESPOTIFY -#include "input/despotify_input_plugin.h" -#endif - -#include <glib.h> - -const struct input_plugin *const input_plugins[] = { - &input_plugin_file, -#ifdef ENABLE_ARCHIVE - &input_plugin_archive, -#endif -#ifdef ENABLE_CURL - &input_plugin_curl, -#endif -#ifdef ENABLE_SOUP - &input_plugin_soup, -#endif -#ifdef HAVE_FFMPEG - &input_plugin_ffmpeg, -#endif -#ifdef ENABLE_MMS - &input_plugin_mms, -#endif -#ifdef ENABLE_CDIO_PARANOIA - &input_plugin_cdio_paranoia, -#endif -#ifdef ENABLE_DESPOTIFY - &input_plugin_despotify, -#endif - NULL -}; - -bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/input_registry.h b/src/input_registry.h deleted file mode 100644 index 4f5fff8da..000000000 --- a/src/input_registry.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_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[]; - -#define input_plugins_for_each(plugin) \ - for (const struct input_plugin *plugin, \ - *const*input_plugin_iterator = &input_plugins[0]; \ - (plugin = *input_plugin_iterator) != NULL; \ - ++input_plugin_iterator) - -#define input_plugins_for_each_enabled(plugin) \ - input_plugins_for_each(plugin) \ - if (input_plugins_enabled[input_plugin_iterator - input_plugins]) - -#endif diff --git a/src/input_stream.c b/src/input_stream.c deleted file mode 100644 index e445dca6c..000000000 --- a/src/input_stream.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "input_stream.h" -#include "input_registry.h" -#include "input_plugin.h" -#include "input/rewind_input_plugin.h" - -#include <glib.h> -#include <assert.h> - -static inline GQuark -input_quark(void) -{ - return g_quark_from_static_string("input"); -} - -struct input_stream * -input_stream_open(const char *url, - GMutex *mutex, GCond *cond, - GError **error_r) -{ - GError *error = NULL; - - assert(mutex != NULL); - assert(error_r == NULL || *error_r == NULL); - - input_plugins_for_each_enabled(plugin) { - struct input_stream *is; - - is = plugin->open(url, mutex, cond, &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); - - is = input_rewind_open(is); - - 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 NULL; -} - -bool -input_stream_check(struct input_stream *is, GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->check == NULL || - is->plugin->check(is, error_r); -} - -void -input_stream_update(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->update != NULL) - is->plugin->update(is); -} - -void -input_stream_wait_ready(struct input_stream *is) -{ - assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - - while (true) { - input_stream_update(is); - if (is->ready) - break; - - g_cond_wait(is->cond, is->mutex); - } -} - -void -input_stream_lock_wait_ready(struct input_stream *is) -{ - assert(is != NULL); - assert(is->mutex != NULL); - assert(is->cond != NULL); - - g_mutex_lock(is->mutex); - input_stream_wait_ready(is); - g_mutex_unlock(is->mutex); -} - -bool -input_stream_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->seek == NULL) - return false; - - return is->plugin->seek(is, offset, whence, error_r); -} - -bool -input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->seek == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_seek(is, offset, whence, error_r); - - g_mutex_lock(is->mutex); - bool success = input_stream_seek(is, offset, whence, error_r); - g_mutex_unlock(is->mutex); - return success; -} - -struct tag * -input_stream_tag(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->tag != NULL - ? is->plugin->tag(is) - : NULL; -} - -struct tag * -input_stream_lock_tag(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->plugin->tag == NULL) - return false; - - if (is->mutex == NULL) - /* no locking */ - return input_stream_tag(is); - - g_mutex_lock(is->mutex); - struct tag *tag = input_stream_tag(is); - g_mutex_unlock(is->mutex); - return tag; -} - -bool -input_stream_available(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - return is->plugin->available != NULL - ? is->plugin->available(is) - : true; -} - -size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - assert(ptr != NULL); - assert(size > 0); - - return is->plugin->read(is, ptr, size, error_r); -} - -size_t -input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r) -{ - assert(ptr != NULL); - assert(size > 0); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_read(is, ptr, size, error_r); - - g_mutex_lock(is->mutex); - size_t nbytes = input_stream_read(is, ptr, size, error_r); - g_mutex_unlock(is->mutex); - return nbytes; -} - -void input_stream_close(struct input_stream *is) -{ - is->plugin->close(is); -} - -bool input_stream_eof(struct input_stream *is) -{ - return is->plugin->eof(is); -} - -bool -input_stream_lock_eof(struct input_stream *is) -{ - assert(is != NULL); - assert(is->plugin != NULL); - - if (is->mutex == NULL) - /* no locking */ - return input_stream_eof(is); - - g_mutex_lock(is->mutex); - bool eof = input_stream_eof(is); - g_mutex_unlock(is->mutex); - return eof; -} - diff --git a/src/input_stream.h b/src/input_stream.h deleted file mode 100644 index 10ad97161..000000000 --- a/src/input_stream.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_STREAM_H -#define MPD_INPUT_STREAM_H - -#include "check.h" -#include "gcc.h" - -#include <glib.h> - -#include <stddef.h> -#include <stdbool.h> -#include <sys/types.h> - -struct input_stream { - /** - * the plugin which implements this input stream - */ - const struct input_plugin *plugin; - - /** - * The absolute URI which was used to open this stream. May - * be NULL if this is unknown. - */ - char *uri; - - /** - * A mutex that protects the mutable attributes of this object - * and its implementation. It must be locked before calling - * any of the public methods. - * - * This object is allocated by the client, and the client is - * responsible for freeing it. - */ - GMutex *mutex; - - /** - * A cond that gets signalled when the state of this object - * changes from the I/O thread. The client of this object may - * wait on it. Optional, may be NULL. - * - * This object is allocated by the client, and the client is - * responsible for freeing it. - */ - GCond *cond; - - /** - * indicates whether the stream is ready for reading and - * whether the other attributes in this struct are valid - */ - bool ready; - - /** - * if true, then the stream is fully seekable - */ - bool seekable; - - /** - * the size of the resource, or -1 if unknown - */ - goffset size; - - /** - * the current offset within the stream - */ - goffset offset; - - /** - * the MIME content type of the resource, or NULL if unknown - */ - char *mime; -}; - -/** - * Opens a new input stream. You may not access it until the "ready" - * flag is set. - * - * @param mutex a mutex that is used to protect this object; must be - * locked before calling any of the public methods - * @param cond a cond that gets signalled when the state of - * this object changes; may be NULL if the caller doesn't want to get - * notifications - * @return an #input_stream object on success, NULL on error - */ -gcc_nonnull(1, 2) -G_GNUC_MALLOC -struct input_stream * -input_stream_open(const char *uri, - GMutex *mutex, GCond *cond, - GError **error_r); - -/** - * Close the input stream and free resources. - * - * The caller must not lock the mutex. - */ -gcc_nonnull(1) -void -input_stream_close(struct input_stream *is); - -gcc_nonnull(1) -static inline void -input_stream_lock(struct input_stream *is) -{ - g_mutex_lock(is->mutex); -} - -gcc_nonnull(1) -static inline void -input_stream_unlock(struct input_stream *is) -{ - g_mutex_unlock(is->mutex); -} - -/** - * Check for errors that may have occurred in the I/O thread. - * - * @return false on error - */ -gcc_nonnull(1) -bool -input_stream_check(struct input_stream *is, GError **error_r); - -/** - * Update the public attributes. Call before accessing attributes - * such as "ready" or "offset". - */ -gcc_nonnull(1) -void -input_stream_update(struct input_stream *is); - -/** - * Wait until the stream becomes ready. - * - * The caller must lock the mutex. - */ -gcc_nonnull(1) -void -input_stream_wait_ready(struct input_stream *is); - -/** - * Wrapper for input_stream_wait_locked() which locks and unlocks the - * mutex; the caller must not be holding it already. - */ -gcc_nonnull(1) -void -input_stream_lock_wait_ready(struct input_stream *is); - -/** - * Seeks to the specified position in the stream. This will most - * likely fail if the "seekable" flag is false. - * - * The caller must lock the mutex. - * - * @param is the input_stream object - * @param offset the relative offset - * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END - */ -gcc_nonnull(1) -bool -input_stream_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r); - -/** - * Wrapper for input_stream_seek() which locks and unlocks the - * mutex; the caller must not be holding it already. - */ -gcc_nonnull(1) -bool -input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, - GError **error_r); - -/** - * Returns true if the stream has reached end-of-file. - * - * The caller must lock the mutex. - */ -gcc_nonnull(1) -G_GNUC_PURE -bool input_stream_eof(struct input_stream *is); - -/** - * Wrapper for input_stream_eof() which locks and unlocks the mutex; - * the caller must not be holding it already. - */ -gcc_nonnull(1) -G_GNUC_PURE -bool -input_stream_lock_eof(struct input_stream *is); - -/** - * Reads the tag from the stream. - * - * The caller must lock the mutex. - * - * @return a tag object which must be freed with tag_free(), or NULL - * if the tag has not changed since the last call - */ -gcc_nonnull(1) -G_GNUC_MALLOC -struct tag * -input_stream_tag(struct input_stream *is); - -/** - * Wrapper for input_stream_tag() which locks and unlocks the - * mutex; the caller must not be holding it already. - */ -gcc_nonnull(1) -G_GNUC_MALLOC -struct tag * -input_stream_lock_tag(struct input_stream *is); - -/** - * Returns true if the next read operation will not block: either data - * is available, or end-of-stream has been reached, or an error has - * occurred. - * - * The caller must lock the mutex. - */ -gcc_nonnull(1) -G_GNUC_PURE -bool -input_stream_available(struct input_stream *is); - -/** - * Reads data from the stream into the caller-supplied buffer. - * Returns 0 on error or eof (check with input_stream_eof()). - * - * The caller must lock the mutex. - * - * @param is the input_stream object - * @param ptr the buffer to read into - * @param size the maximum number of bytes to read - * @return the number of bytes read - */ -gcc_nonnull(1, 2) -size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r); - -/** - * Wrapper for input_stream_tag() which locks and unlocks the - * mutex; the caller must not be holding it already. - */ -gcc_nonnull(1, 2) -size_t -input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, - GError **error_r); - -#endif diff --git a/src/io_thread.c b/src/io_thread.c deleted file mode 100644 index 7c080adcb..000000000 --- a/src/io_thread.c +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "io_thread.h" - -#include <assert.h> - -static struct { - GMutex *mutex; - GCond *cond; - - GMainContext *context; - GMainLoop *loop; - GThread *thread; -} io; - -void -io_thread_run(void) -{ - assert(io_thread_inside()); - assert(io.context != NULL); - assert(io.loop != NULL); - - g_main_loop_run(io.loop); -} - -static gpointer -io_thread_func(G_GNUC_UNUSED gpointer arg) -{ - /* lock+unlock to synchronize with io_thread_start(), to be - sure that io.thread is set */ - g_mutex_lock(io.mutex); - g_mutex_unlock(io.mutex); - - io_thread_run(); - return NULL; -} - -void -io_thread_init(void) -{ - assert(io.context == NULL); - assert(io.loop == NULL); - assert(io.thread == NULL); - - io.mutex = g_mutex_new(); - io.cond = g_cond_new(); - io.context = g_main_context_new(); - io.loop = g_main_loop_new(io.context, false); -} - -bool -io_thread_start(GError **error_r) -{ - assert(io.context != NULL); - assert(io.loop != NULL); - assert(io.thread == NULL); - - g_mutex_lock(io.mutex); - io.thread = g_thread_create(io_thread_func, NULL, true, error_r); - g_mutex_unlock(io.mutex); - if (io.thread == NULL) - return false; - - return true; -} - -void -io_thread_quit(void) -{ - assert(io.loop != NULL); - - g_main_loop_quit(io.loop); -} - -void -io_thread_deinit(void) -{ - if (io.thread != NULL) { - io_thread_quit(); - - g_thread_join(io.thread); - } - - if (io.loop != NULL) - g_main_loop_unref(io.loop); - - if (io.context != NULL) - g_main_context_unref(io.context); - - g_cond_free(io.cond); - g_mutex_free(io.mutex); -} - -GMainContext * -io_thread_context(void) -{ - return io.context; -} - -bool -io_thread_inside(void) -{ - return io.thread != NULL && g_thread_self() == io.thread; -} - -guint -io_thread_idle_add(GSourceFunc function, gpointer data) -{ - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, NULL); - guint id = g_source_attach(source, io.context); - g_source_unref(source); - return id; -} - -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new(interval_ms); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new_seconds(interval); - g_source_set_callback(source, function, data, NULL); - g_source_attach(source, io.context); - return source; -} - -struct call_data { - GThreadFunc function; - gpointer data; - bool done; - gpointer result; -}; - -static gboolean -io_thread_call_func(gpointer _data) -{ - struct call_data *data = _data; - - gpointer result = data->function(data->data); - - g_mutex_lock(io.mutex); - data->done = true; - data->result = result; - g_cond_broadcast(io.cond); - g_mutex_unlock(io.mutex); - - return false; -} - -gpointer -io_thread_call(GThreadFunc function, gpointer _data) -{ - assert(io.thread != NULL); - - if (io_thread_inside()) - /* we're already in the I/O thread - no - synchronization needed */ - return function(_data); - - struct call_data data = { - .function = function, - .data = _data, - .done = false, - }; - - io_thread_idle_add(io_thread_call_func, &data); - - g_mutex_lock(io.mutex); - while (!data.done) - g_cond_wait(io.cond, io.mutex); - g_mutex_unlock(io.mutex); - - return data.result; -} diff --git a/src/io_thread.h b/src/io_thread.h deleted file mode 100644 index 8ff5a71e5..000000000 --- a/src/io_thread.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_IO_THREAD_H -#define MPD_IO_THREAD_H - -#include <glib.h> -#include <stdbool.h> - -void -io_thread_init(void); - -bool -io_thread_start(GError **error_r); - -/** - * Run the I/O event loop synchronously in the current thread. This - * can be called instead of io_thread_start(). For testing purposes - * only. - */ -void -io_thread_run(void); - -/** - * Ask the I/O thread to quit, but does not wait for it. Usually, you - * don't need to call this function, because io_thread_deinit() - * includes this. - */ -void -io_thread_quit(void); - -void -io_thread_deinit(void); - -G_GNUC_PURE -GMainContext * -io_thread_context(void); - -/** - * Is the current thread the I/O thread? - */ -G_GNUC_PURE -bool -io_thread_inside(void); - -guint -io_thread_idle_add(GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data); - -G_GNUC_MALLOC -GSource * -io_thread_timeout_add_seconds(guint interval, - GSourceFunc function, gpointer data); - -/** - * Call a function synchronously in the I/O thread. - */ -gpointer -io_thread_call(GThreadFunc function, gpointer data); - -#endif diff --git a/src/listen.c b/src/listen.c deleted file mode 100644 index 90e13b9c1..000000000 --- a/src/listen.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "listen.h" -#include "server_socket.h" -#include "client.h" -#include "conf.h" -#include "main.h" - -#include <string.h> -#include <assert.h> - -#ifdef ENABLE_SYSTEMD_DAEMON -#include <systemd/sd-daemon.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "listen" - -#define DEFAULT_PORT 6600 - -static struct server_socket *listen_socket; -int listen_port; - -static void -listen_callback(int fd, const struct sockaddr *address, - size_t address_length, int uid, G_GNUC_UNUSED void *ctx) -{ - client_new(global_player_control, fd, address, address_length, uid); -} - -static bool -listen_add_config_param(unsigned int port, - const struct config_param *param, - GError **error_r) -{ - assert(param != NULL); - - if (0 == strcmp(param->value, "any")) { - return server_socket_add_port(listen_socket, port, error_r); - } else if (param->value[0] == '/') { - return server_socket_add_path(listen_socket, param->value, - error_r); - } else { - return server_socket_add_host(listen_socket, param->value, - port, error_r); - } -} - -static bool -listen_systemd_activation(GError **error_r) -{ -#ifdef ENABLE_SYSTEMD_DAEMON - int n = sd_listen_fds(true); - if (n <= 0) { - if (n < 0) - g_warning("sd_listen_fds() failed: %s", - g_strerror(-n)); - return false; - } - - for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n; - i != end; ++i) - if (!server_socket_add_fd(listen_socket, i, error_r)) - return false; - - return true; -#else - (void)error_r; - return false; -#endif -} - -bool -listen_global_init(GError **error_r) -{ - int port = config_get_positive(CONF_PORT, DEFAULT_PORT); - const struct config_param *param = - config_get_next_param(CONF_BIND_TO_ADDRESS, NULL); - bool success; - GError *error = NULL; - - listen_socket = server_socket_new(listen_callback, NULL); - - if (listen_systemd_activation(&error)) - return true; - - if (error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - if (param != NULL) { - /* "bind_to_address" is configured, create listeners - for all values */ - - do { - success = listen_add_config_param(port, param, &error); - 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); - } while (param != NULL); - } else { - /* no "bind_to_address" configured, bind the - configured port on all interfaces */ - - 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"); - - assert(listen_socket != NULL); - - server_socket_free(listen_socket); -} diff --git a/src/listen.h b/src/listen.h deleted file mode 100644 index 246e83706..000000000 --- a/src/listen.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LISTEN_H -#define MPD_LISTEN_H - -#include <glib.h> - -#include <stdbool.h> - -extern int listen_port; - -bool -listen_global_init(GError **error_r); - -void listen_global_finish(void); - -#endif diff --git a/src/locate.c b/src/locate.c deleted file mode 100644 index c9684d2b6..000000000 --- a/src/locate.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "locate.h" -#include "path.h" -#include "tag.h" -#include "song.h" - -#include <glib.h> - -#include <stdlib.h> - -#define LOCATE_TAG_FILE_KEY "file" -#define LOCATE_TAG_FILE_KEY_OLD "filename" -#define LOCATE_TAG_ANY_KEY "any" - -int -locate_parse_type(const char *str) -{ - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) || - 0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) - return LOCATE_TAG_FILE_TYPE; - - if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) - return LOCATE_TAG_ANY_TYPE; - - enum tag_type i = tag_name_parse_i(str); - if (i != TAG_NUM_OF_ITEM_TYPES) - return i; - - return -1; -} - -static bool -locate_item_init(struct locate_item *item, - const char *type_string, const char *needle) -{ - item->tag = locate_parse_type(type_string); - - if (item->tag < 0) - return false; - - item->needle = g_strdup(needle); - - return true; -} - -void -locate_item_list_free(struct locate_item_list *list) -{ - for (unsigned i = 0; i < list->length; ++i) - g_free(list->items[i].needle); - - g_free(list); -} - -struct locate_item_list * -locate_item_list_new(unsigned length) -{ - struct locate_item_list *list = - g_malloc0(sizeof(*list) - sizeof(list->items[0]) + - length * sizeof(list->items[0])); - list->length = length; - - return list; -} - -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc) -{ - if (argc % 2 != 0) - return NULL; - - struct locate_item_list *list = locate_item_list_new(argc / 2); - - for (unsigned i = 0; i < list->length; ++i) { - if (!locate_item_init(&list->items[i], argv[i * 2], - argv[i * 2 + 1])) { - locate_item_list_free(list); - return NULL; - } - } - - return list; -} - -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list) -{ - struct locate_item_list *new_list = locate_item_list_new(list->length); - - for (unsigned i = 0; i < list->length; i++){ - new_list->items[i].needle = - g_utf8_casefold(list->items[i].needle, -1); - new_list->items[i].tag = list->items[i].tag; - } - - return new_list; -} - -void -locate_item_free(struct locate_item *item) -{ - g_free(item->needle); - g_free(item); -} - -static bool -locate_tag_search(const struct song *song, enum tag_type type, const char *str) -{ - bool ret = false; - - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - char *p = g_utf8_casefold(uri, -1); - g_free(uri); - - if (strstr(p, str)) - ret = true; - g_free(p); - if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) - return ret; - } - - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items && !ret; i++) { - visited_types[song->tag->items[i]->type] = true; - if ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - char *duplicate = g_utf8_casefold(song->tag->items[i]->value, -1); - if (*str && strstr(duplicate, str)) - ret = true; - g_free(duplicate); - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return ret; -} - -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_search(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; -} - -static bool -locate_tag_match(const struct song *song, enum tag_type type, const char *str) -{ - if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) { - char *uri = song_get_uri(song); - bool matches = strcmp(str, uri) == 0; - g_free(uri); - - if (matches) - return true; - - if (type == LOCATE_TAG_FILE_TYPE) - return false; - } - - if (!song->tag) - return false; - - bool visited_types[TAG_NUM_OF_ITEM_TYPES]; - memset(visited_types, 0, sizeof(visited_types)); - - for (unsigned i = 0; i < song->tag->num_items; i++) { - visited_types[song->tag->items[i]->type] = true; - if ((int)type != LOCATE_TAG_ANY_TYPE && - song->tag->items[i]->type != type) { - continue; - } - - if (0 == strcmp(str, song->tag->items[i]->value)) - return true; - } - - /** If the search critieron was not visited during the sweep - * through the song's tag, it means this field is absent from - * the tag or empty. Thus, if the searched string is also - * empty (first char is a \0), then it's a match as well and - * we should return true. - */ - if (!*str && !visited_types[type]) - return true; - - return false; -} - -bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < criteria->length; i++) - if (!locate_tag_match(song, criteria->items[i].tag, - criteria->items[i].needle)) - return false; - - return true; -} diff --git a/src/locate.h b/src/locate.h deleted file mode 100644 index ec20ded24..000000000 --- a/src/locate.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LOCATE_H -#define MPD_LOCATE_H - -#include "gcc.h" - -#include <stdint.h> -#include <stdbool.h> - -#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 -#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 - -struct song; - -/* struct used for search, find, list queries */ -struct locate_item { - int8_t tag; - /* what we are looking for */ - char *needle; -}; - -/** - * An array of struct locate_item objects. - */ -struct locate_item_list { - /** number of items */ - unsigned length; - - /** this is a variable length array */ - struct locate_item items[1]; -}; - -int -locate_parse_type(const char *str); - -/** - * Allocates a new struct locate_item_list, and initializes all - * members with zero bytes. - */ -struct locate_item_list * -locate_item_list_new(unsigned length); - -/* return number of items or -1 on error */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_parse(char *argv[], int argc); - -/** - * Duplicate the struct locate_item_list object and convert all - * needles with g_utf8_casefold(). - */ -gcc_nonnull(1) -struct locate_item_list * -locate_item_list_casefold(const struct locate_item_list *list); - -gcc_nonnull(1) -void -locate_item_list_free(struct locate_item_list *list); - -gcc_nonnull(1) -void -locate_item_free(struct locate_item *item); - -gcc_nonnull(1,2) -bool -locate_song_search(const struct song *song, - const struct locate_item_list *criteria); - -gcc_nonnull(1,2) -bool -locate_song_match(const struct song *song, - const struct locate_item_list *criteria); - -#endif diff --git a/src/log.c b/src/log.c deleted file mode 100644 index 2d3c7cafd..000000000 --- a/src/log.c +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "log.h" -#include "conf.h" -#include "utils.h" -#include "fd_util.h" -#include "mpd_error.h" - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <string.h> -#include <stdarg.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include <unistd.h> -#include <errno.h> -#include <glib.h> - -#ifdef HAVE_SYSLOG -#include <syslog.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "log" - -#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO - -#define LOG_DATE_BUF_SIZE 16 -#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) - -static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE; - -static const char *log_charset; - -static bool stdout_mode = true; -static int out_fd; -static char *out_filename; - -static void redirect_logs(int fd) -{ - assert(fd >= 0); - if (dup2(fd, STDOUT_FILENO) < 0) - MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno)); - if (dup2(fd, STDERR_FILENO) < 0) - MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno)); -} - -static const char *log_date(void) -{ - static char buf[LOG_DATE_BUF_SIZE]; - time_t t = time(NULL); - strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); - return buf; -} - -/** - * Determines the length of the string excluding trailing whitespace - * characters. - */ -static int -chomp_length(const char *p) -{ - size_t length = strlen(p); - - while (length > 0 && g_ascii_isspace(p[length - 1])) - --length; - - return (int)length; -} - -static void -file_log_func(const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - char *converted; - - if (log_level > log_threshold) - return; - - if (log_charset != NULL) { - converted = g_convert_with_fallback(message, -1, - log_charset, "utf-8", - NULL, NULL, NULL, NULL); - if (converted != NULL) - message = converted; - } else - converted = NULL; - - if (log_domain == NULL) - log_domain = ""; - - fprintf(stderr, "%s%s%s%.*s\n", - stdout_mode ? "" : log_date(), - log_domain, *log_domain == 0 ? "" : ": ", - chomp_length(message), message); - - g_free(converted); -} - -static void -log_init_stdout(void) -{ - g_log_set_default_handler(file_log_func, NULL); -} - -static int -open_log_file(void) -{ - assert(out_filename != NULL); - - return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); -} - -static bool -log_init_file(unsigned line, GError **error_r) -{ - assert(out_filename != NULL); - - out_fd = open_log_file(); - if (out_fd < 0) { - g_set_error(error_r, log_quark(), errno, - "failed to open log file \"%s\" (config line %u): %s", - out_filename, line, g_strerror(errno)); - return false; - } - - g_log_set_default_handler(file_log_func, NULL); - return true; -} - -#ifdef HAVE_SYSLOG - -static int -glib_to_syslog_level(GLogLevelFlags log_level) -{ - switch (log_level & G_LOG_LEVEL_MASK) { - case G_LOG_LEVEL_ERROR: - case G_LOG_LEVEL_CRITICAL: - return LOG_ERR; - - case G_LOG_LEVEL_WARNING: - return LOG_WARNING; - - case G_LOG_LEVEL_MESSAGE: - return LOG_NOTICE; - - case G_LOG_LEVEL_INFO: - return LOG_INFO; - - case G_LOG_LEVEL_DEBUG: - return LOG_DEBUG; - - default: - return LOG_NOTICE; - } -} - -static void -syslog_log_func(const gchar *log_domain, - GLogLevelFlags log_level, const gchar *message, - G_GNUC_UNUSED gpointer user_data) -{ - if (stdout_mode) { - /* fall back to the file log function during - startup */ - file_log_func(log_domain, log_level, - message, user_data); - return; - } - - if (log_level > log_threshold) - return; - - if (log_domain == NULL) - log_domain = ""; - - syslog(glib_to_syslog_level(log_level), "%s%s%.*s", - log_domain, *log_domain == 0 ? "" : ": ", - chomp_length(message), message); -} - -static void -log_init_syslog(void) -{ - assert(out_filename == NULL); - - openlog(PACKAGE, 0, LOG_DAEMON); - g_log_set_default_handler(syslog_log_func, NULL); -} - -#endif - -static inline GLogLevelFlags -parse_log_level(const char *value, unsigned line) -{ - if (0 == strcmp(value, "default")) - return G_LOG_LEVEL_MESSAGE; - if (0 == strcmp(value, "secure")) - return LOG_LEVEL_SECURE; - else if (0 == strcmp(value, "verbose")) - return G_LOG_LEVEL_DEBUG; - else { - MPD_ERROR("unknown log level \"%s\" at line %u\n", - value, line); - return G_LOG_LEVEL_MESSAGE; - } -} - -void -log_early_init(bool verbose) -{ - if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; - - log_init_stdout(); -} - -bool -log_init(bool verbose, bool use_stdout, GError **error_r) -{ - const struct config_param *param; - - g_get_charset(&log_charset); - - if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; - else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL) - log_threshold = parse_log_level(param->value, param->line); - - if (use_stdout) { - log_init_stdout(); - return true; - } else { - param = config_get_param(CONF_LOG_FILE); - if (param == NULL) { -#ifdef HAVE_SYSLOG - /* no configuration: default to syslog (if - available) */ - log_init_syslog(); - return true; -#else - g_set_error(error_r, log_quark(), 0, - "config parameter \"%s\" not found", - CONF_LOG_FILE); - return false; -#endif -#ifdef HAVE_SYSLOG - } else if (strcmp(param->value, "syslog") == 0) { - log_init_syslog(); - return true; -#endif - } else { - out_filename = config_dup_path(CONF_LOG_FILE, error_r); - return out_filename != NULL && - log_init_file(param->line, error_r); - } - } -} - -static void -close_log_files(void) -{ - if (stdout_mode) - return; - -#ifdef HAVE_SYSLOG - if (out_filename == NULL) - closelog(); -#endif -} - -void -log_deinit(void) -{ - close_log_files(); - g_free(out_filename); -} - - -void setup_log_output(bool use_stdout) -{ - fflush(NULL); - if (!use_stdout) { -#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); - } - - stdout_mode = false; - log_charset = NULL; - } -} - -int cycle_log_files(void) -{ - int fd; - - if (stdout_mode || out_filename == NULL) - return 0; - assert(out_filename); - - g_debug("Cycling log files...\n"); - close_log_files(); - - fd = open_log_file(); - if (fd < 0) { - g_warning("error re-opening log file: %s\n", out_filename); - return -1; - } - - redirect_logs(fd); - g_debug("Done cycling log files\n"); - return 0; -} diff --git a/src/log.h b/src/log.h deleted file mode 100644 index 683ff3e9f..000000000 --- a/src/log.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LOG_H -#define MPD_LOG_H - -#include <glib.h> -#include <stdbool.h> - -G_GNUC_CONST -static inline GQuark -log_quark(void) -{ - return g_quark_from_static_string("log"); -} - -/** - * Configure a logging destination for daemon startup, before the - * configuration file is read. This allows the daemon to use the - * logging library (and the command line verbose level) before it's - * daemonized. - * - * @param verbose true when the program is started with --verbose - */ -void -log_early_init(bool verbose); - -bool -log_init(bool verbose, bool use_stdout, GError **error_r); - -void -log_deinit(void); - -void setup_log_output(bool use_stdout); - -int cycle_log_files(void); - -#endif /* LOG_H */ diff --git a/src/ls.c b/src/ls.c deleted file mode 100644 index 310c2d7b6..000000000 --- a/src/ls.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ls.h" -#include "uri.h" -#include "client.h" - -#include <assert.h> -#include <string.h> - - -/** - * file:// is not included in remoteUrlPrefixes, the connection method - * is detected at runtime and displayed as a urlhandler if the client is - * connected by IPC socket. - */ -static const char *remoteUrlPrefixes[] = { -#if defined(ENABLE_CURL) || defined(ENABLE_SOUP) - "http://", -#endif -#ifdef ENABLE_MMS - "mms://", - "mmsh://", - "mmst://", - "mmsu://", -#endif -#ifdef HAVE_FFMPEG - "gopher://", - "rtp://", - "rtsp://", - "rtmp://", - "rtmpt://", - "rtmps://", -#endif -#ifdef ENABLE_CDIO_PARANOIA - "cdda://", -#endif -#ifdef ENABLE_DESPOTIFY - "spt://", -#endif - NULL -}; - -void print_supported_uri_schemes_to_fp(FILE *fp) -{ - const char **prefixes = remoteUrlPrefixes; - -#ifdef HAVE_UN - fprintf(fp, " file://"); -#endif - while (*prefixes) { - fprintf(fp, " %s", *prefixes); - prefixes++; - } - fprintf(fp,"\n"); -} - -void print_supported_uri_schemes(struct client *client) -{ - const char **prefixes = remoteUrlPrefixes; - - while (*prefixes) { - client_printf(client, "handler: %s\n", *prefixes); - prefixes++; - } -} - -bool uri_supported_scheme(const char *uri) -{ - const char **urlPrefixes = remoteUrlPrefixes; - - assert(uri_has_scheme(uri)); - - while (*urlPrefixes) { - if (g_str_has_prefix(uri, *urlPrefixes)) - return true; - urlPrefixes++; - } - - return false; -} diff --git a/src/ls.cxx b/src/ls.cxx new file mode 100644 index 000000000..06237641e --- /dev/null +++ b/src/ls.cxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ls.hxx" +#include "util/UriUtil.hxx" +#include "Client.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + + +/** + * file:// is not included in remoteUrlPrefixes, the connection method + * is detected at runtime and displayed as a urlhandler if the client is + * connected by IPC socket. + */ +static const char *remoteUrlPrefixes[] = { +#if defined(ENABLE_CURL) + "http://", + "https://", +#endif +#ifdef ENABLE_MMS + "mms://", + "mmsh://", + "mmst://", + "mmsu://", +#endif +#ifdef HAVE_FFMPEG + "gopher://", + "rtp://", + "rtsp://", + "rtmp://", + "rtmpt://", + "rtmps://", +#endif +#ifdef ENABLE_CDIO_PARANOIA + "cdda://", +#endif +#ifdef ENABLE_DESPOTIFY + "spt://", +#endif + NULL +}; + +void print_supported_uri_schemes_to_fp(FILE *fp) +{ + const char **prefixes = remoteUrlPrefixes; + +#ifdef HAVE_UN + fprintf(fp, " file://"); +#endif + while (*prefixes) { + fprintf(fp, " %s", *prefixes); + prefixes++; + } + fprintf(fp,"\n"); +} + +void print_supported_uri_schemes(Client *client) +{ + const char **prefixes = remoteUrlPrefixes; + + while (*prefixes) { + client_printf(client, "handler: %s\n", *prefixes); + prefixes++; + } +} + +bool uri_supported_scheme(const char *uri) +{ + const char **urlPrefixes = remoteUrlPrefixes; + + assert(uri_has_scheme(uri)); + + while (*urlPrefixes) { + if (g_str_has_prefix(uri, *urlPrefixes)) + return true; + urlPrefixes++; + } + + return false; +} diff --git a/src/ls.h b/src/ls.h deleted file mode 100644 index 15cb01160..000000000 --- a/src/ls.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_LS_H -#define MPD_LS_H - -#include <stdbool.h> -#include <stdio.h> - -struct client; - -/** - * Checks whether the scheme of the specified URI is supported by MPD. - * It is not allowed to pass an URI without a scheme, check with - * uri_has_scheme() first. - */ -bool uri_supported_scheme(const char *url); - -/** - * Send a list of supported URI schemes to the client. This is the - * response to the "urlhandlers" command. - */ -void print_supported_uri_schemes(struct client *client); - -/** - * Send a list of supported URI schemes to a file pointer. - */ -void print_supported_uri_schemes_to_fp(FILE *fp); - -#endif diff --git a/src/ls.hxx b/src/ls.hxx new file mode 100644 index 000000000..8ae5a58fd --- /dev/null +++ b/src/ls.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LS_HXX +#define MPD_LS_HXX + +#include <stdio.h> + +class Client; + +/** + * Checks whether the scheme of the specified URI is supported by MPD. + * It is not allowed to pass an URI without a scheme, check with + * uri_has_scheme() first. + */ +bool uri_supported_scheme(const char *url); + +/** + * Send a list of supported URI schemes to the client. This is the + * response to the "urlhandlers" command. + */ +void print_supported_uri_schemes(Client *client); + +/** + * Send a list of supported URI schemes to a file pointer. + */ +void print_supported_uri_schemes_to_fp(FILE *fp); + +#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 12f8d86f6..000000000 --- a/src/main.c +++ /dev/null @@ -1,541 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "main.h" -#include "daemon.h" -#include "io_thread.h" -#include "client.h" -#include "client_idle.h" -#include "idle.h" -#include "command.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "database.h" -#include "update.h" -#include "player_thread.h" -#include "listen.h" -#include "cmdline.h" -#include "conf.h" -#include "path.h" -#include "mapper.h" -#include "chunk.h" -#include "player_control.h" -#include "stats.h" -#include "sig_handlers.h" -#include "audio_config.h" -#include "output_all.h" -#include "volume.h" -#include "log.h" -#include "permission.h" -#include "pcm_resample.h" -#include "replay_gain_config.h" -#include "decoder_list.h" -#include "input_init.h" -#include "playlist_list.h" -#include "state_file.h" -#include "tag.h" -#include "dbUtils.h" -#include "zeroconf.h" -#include "event_pipe.h" -#include "tag_pool.h" -#include "mpd_error.h" - -#ifdef ENABLE_INOTIFY -#include "inotify_update.h" -#endif - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#endif - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#endif - -#include <glib.h> - -#include <unistd.h> -#include <stdlib.h> -#include <errno.h> -#include <string.h> - -#ifdef HAVE_LOCALE_H -#include <locale.h> -#endif - -#ifdef WIN32 -#include <winsock2.h> -#include <ws2tcpip.h> -#endif - -enum { - DEFAULT_BUFFER_SIZE = 2048, - DEFAULT_BUFFER_BEFORE_PLAY = 10, -}; - -GThread *main_task; -GMainLoop *main_loop; - -GCond *main_cond; - -struct player_control *global_player_control; - -static bool -glue_daemonize_init(const struct options *options, GError **error_r) -{ - GError *error = NULL; - - char *pid_file = config_dup_path(CONF_PID_FILE, &error); - if (pid_file == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - daemonize_init(config_get_string(CONF_USER, NULL), - config_get_string(CONF_GROUP, NULL), - pid_file); - g_free(pid_file); - - if (options->kill) - daemonize_kill(); - - return true; -} - -static bool -glue_mapper_init(GError **error_r) -{ - GError *error = NULL; - char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error); - if (music_dir == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error); - if (playlist_dir == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - if (music_dir == NULL) - music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); - - mapper_init(music_dir, playlist_dir); - - g_free(music_dir); - g_free(playlist_dir); - return true; -} - -/** - * Returns the database. If this function returns false, this has not - * succeeded, and the caller should create the database after the - * process has been daemonized. - */ -static bool -glue_db_init_and_load(void) -{ - const struct config_param *path = config_get_param(CONF_DB_FILE); - - GError *error = NULL; - bool ret; - - if (!mapper_has_music_directory()) { - if (path != NULL) - g_message("Found " CONF_DB_FILE " setting without " - CONF_MUSIC_DIR " - disabling database"); - db_init(NULL, NULL); - return true; - } - - if (path == NULL) - MPD_ERROR(CONF_DB_FILE " setting missing"); - - if (!db_init(path, &error)) - MPD_ERROR("%s", error->message); - - ret = db_load(&error); - if (!ret) - MPD_ERROR("%s", error->message); - - /* run database update after daemonization? */ - return db_exists(); -} - -/** - * Configure and initialize the sticker subsystem. - */ -static void -glue_sticker_init(void) -{ -#ifdef ENABLE_SQLITE - GError *error = NULL; - char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error); - if (sticker_file == NULL && error != NULL) - MPD_ERROR("%s", error->message); - - if (!sticker_global_init(sticker_file, &error)) - MPD_ERROR("%s", error->message); - - g_free(sticker_file); -#endif -} - -static bool -glue_state_file_init(GError **error_r) -{ - GError *error = NULL; - - char *path = config_dup_path(CONF_STATE_FILE, &error); - if (path == NULL && error != NULL) { - g_propagate_error(error_r, error); - return false; - } - - state_file_init(path, global_player_control); - g_free(path); - - return true; -} - -/** - * Windows-only initialization of the Winsock2 library. - */ -static void winsock_init(void) -{ -#ifdef WIN32 - WSADATA sockinfo; - int retval; - - retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); - if(retval != 0) - { - MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n", - retval); - } - - if (LOBYTE(sockinfo.wVersion) != 2) - { - 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. - */ -static void -initialize_decoder_and_player(void) -{ - const struct config_param *param; - char *test; - size_t buffer_size; - float perc; - unsigned buffered_chunks; - unsigned buffered_before_play; - - param = config_get_param(CONF_AUDIO_BUFFER_SIZE); - if (param != NULL) { - long tmp = strtol(param->value, &test, 10); - if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) - MPD_ERROR("buffer size \"%s\" is not a positive integer, " - "line %i\n", param->value, param->line); - buffer_size = tmp; - } else - buffer_size = DEFAULT_BUFFER_SIZE; - - buffer_size *= 1024; - - buffered_chunks = buffer_size / CHUNK_SIZE; - - if (buffered_chunks >= 1 << 15) - 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) { - 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; - - buffered_before_play = (perc / 100) * buffered_chunks; - if (buffered_before_play > buffered_chunks) - buffered_before_play = buffered_chunks; - - global_player_control = pc_new(buffered_chunks, buffered_before_play); -} - -/** - * event_pipe callback function for PIPE_EVENT_IDLE - */ -static void -idle_event_emitted(void) -{ - /* send "idle" notificaions to all subscribed - clients */ - unsigned flags = idle_get(); - if (flags != 0) - client_manager_idle_add(flags); -} - -/** - * event_pipe callback function for PIPE_EVENT_SHUTDOWN - */ -static void -shutdown_event_emitted(void) -{ - g_main_loop_quit(main_loop); -} - -int main(int argc, char *argv[]) -{ -#ifdef WIN32 - return win32_main(argc, argv); -#else - return mpd_main(argc, argv); -#endif -} - -int mpd_main(int argc, char *argv[]) -{ - struct options options; - clock_t start; - bool create_db; - GError *error = NULL; - bool success; - - daemonize_close_stdin(); - -#ifdef HAVE_LOCALE_H - /* initialize locale */ - setlocale(LC_CTYPE,""); -#endif - - g_set_application_name("Music Player Daemon"); - - /* enable GLib's thread safety code */ - g_thread_init(NULL); - - io_thread_init(); - winsock_init(); - idle_init(); - tag_pool_init(); - config_global_init(); - - success = parse_cmdline(argc, argv, &options, &error); - if (!success) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - if (!glue_daemonize_init(&options, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - stats_global_init(); - tag_lib_init(); - - if (!log_init(options.verbose, options.log_stderr, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - success = listen_global_init(&error); - if (!success) { - 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); - 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(); - - if (!glue_mapper_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - initPermissions(); - playlist_global_init(); - spl_global_init(); -#ifdef ENABLE_ARCHIVE - archive_plugin_init_all(); -#endif - - if (!pcm_resample_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - decoder_plugin_init_all(); - update_global_init(); - - create_db = !glue_db_init_and_load(); - - glue_sticker_init(); - - command_init(); - initialize_decoder_and_player(); - volume_init(); - initAudioConfig(); - audio_output_all_init(global_player_control); - client_manager_init(); - replay_gain_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.log_stderr); - - initSigHandlers(); - - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - initZeroconf(); - - player_create(global_player_control); - - if (create_db) { - /* the database failed to load: recreate the - database */ - unsigned job = update_enqueue(NULL, true); - if (job == 0) - MPD_ERROR("directory update failed"); - } - - if (!glue_state_file_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - success = config_get_bool(CONF_AUTO_UPDATE, false); -#ifdef ENABLE_INOTIFY - 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 - - config_global_check(); - - /* enable all audio outputs (if not already done by - playlist_state_restore() */ - pc_update_audio(global_player_control); - -#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(global_player_control); - pc_kill(global_player_control); - finishZeroconf(); - client_manager_deinit(); - listen_global_finish(); - playlist_global_finish(); - - start = clock(); - db_finish(); - g_debug("db_finish took %f seconds", - ((float)(clock()-start))/CLOCKS_PER_SEC); - -#ifdef ENABLE_SQLITE - sticker_global_finish(); -#endif - - g_cond_free(main_cond); - event_pipe_deinit(); - - playlist_list_global_finish(); - input_stream_global_finish(); - audio_output_all_finish(); - volume_finish(); - mapper_finish(); - path_global_finish(); - finishPermissions(); - pc_free(global_player_control); - command_finish(); - update_global_finish(); - decoder_plugin_deinit_all(); -#ifdef ENABLE_ARCHIVE - archive_plugin_deinit_all(); -#endif - config_global_finish(); - tag_pool_deinit(); - idle_deinit(); - stats_global_finish(); - io_thread_deinit(); - daemonize_finish(); -#ifdef WIN32 - WSACleanup(); -#endif - - log_deinit(); - return EXIT_SUCCESS; -} diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 2a7d75910..000000000 --- a/src/main.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MAIN_H -#define MAIN_H - -#include <glib.h> - -extern GThread *main_task; - -extern GMainLoop *main_loop; - -extern GCond *main_cond; - -extern struct player_control *global_player_control; - -/** - * A entry point for application. - * On non-Windows platforms this is called directly from main() - * 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 deleted file mode 100644 index aac7ad886..000000000 --- a/src/main_win32.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "main.h" - -#ifdef WIN32 - -#include "mpd_error.h" -#include "event_pipe.h" - -#include <glib.h> - -#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, ¤t_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 deleted file mode 100644 index 7db74b1af..000000000 --- a/src/mapper.c +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * 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 <glib.h> - -#include <assert.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <dirent.h> - -/** - * The absolute path of the music directory encoded in UTF-8. - */ -static char *music_dir_utf8; -static size_t music_dir_utf8_length; - -/** - * The absolute path of the music directory encoded in the filesystem - * character set. - */ -static char *music_dir_fs; -static size_t music_dir_fs_length; - -/** - * The absolute path of the playlist directory encoded in the - * filesystem character set. - */ -static char *playlist_dir_fs; - -/** - * Duplicate a string, chop all trailing slashes. - */ -static char * -strdup_chop_slash(const char *path_fs) -{ - size_t length = strlen(path_fs); - - while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR) - --length; - - return g_strndup(path_fs, length); -} - -static void -check_directory(const char *path) -{ - struct stat st; - if (stat(path, &st) < 0) { - g_warning("Failed to stat directory \"%s\": %s", - path, g_strerror(errno)); - return; - } - - if (!S_ISDIR(st.st_mode)) { - g_warning("Not a directory: %s", path); - return; - } - -#ifndef WIN32 - char *x = g_build_filename(path, ".", NULL); - if (stat(x, &st) < 0 && errno == EACCES) - g_warning("No permission to traverse (\"execute\") directory: %s", - path); - g_free(x); -#endif - - DIR *dir = opendir(path); - if (dir != NULL) - closedir(dir); - else if (errno == EACCES) - g_warning("No permission to read directory: %s", path); -} - -static void -mapper_set_music_dir(const char *path_utf8) -{ - music_dir_utf8 = strdup_chop_slash(path_utf8); - music_dir_utf8_length = strlen(music_dir_utf8); - - music_dir_fs = utf8_to_fs_charset(music_dir_utf8); - check_directory(music_dir_fs); - music_dir_fs_length = strlen(music_dir_fs); -} - -static void -mapper_set_playlist_dir(const char *path_utf8) -{ - playlist_dir_fs = utf8_to_fs_charset(path_utf8); - check_directory(playlist_dir_fs); -} - -void mapper_init(const char *_music_dir, const char *_playlist_dir) -{ - if (_music_dir != NULL) - mapper_set_music_dir(_music_dir); - - if (_playlist_dir != NULL) - mapper_set_playlist_dir(_playlist_dir); -} - -void mapper_finish(void) -{ - g_free(music_dir_utf8); - g_free(music_dir_fs); - g_free(playlist_dir_fs); -} - -const char * -mapper_get_music_directory_utf8(void) -{ - return music_dir_utf8; -} - -const char * -mapper_get_music_directory_fs(void) -{ - return music_dir_fs; -} - -const char * -map_to_relative_path(const char *path_utf8) -{ - return music_dir_utf8 != NULL && - memcmp(path_utf8, music_dir_utf8, - music_dir_utf8_length) == 0 && - G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length]) - ? path_utf8 + music_dir_utf8_length + 1 - : path_utf8; -} - -char * -map_uri_fs(const char *uri) -{ - char *uri_fs, *path_fs; - - assert(uri != NULL); - assert(*uri != '/'); - - if (music_dir_fs == NULL) - return NULL; - - uri_fs = utf8_to_fs_charset(uri); - if (uri_fs == NULL) - return NULL; - - path_fs = g_build_filename(music_dir_fs, uri_fs, NULL); - g_free(uri_fs); - - return path_fs; -} - -char * -map_directory_fs(const struct directory *directory) -{ - assert(music_dir_utf8 != NULL); - assert(music_dir_fs != NULL); - - if (directory_is_root(directory)) - return g_strdup(music_dir_fs); - - return map_uri_fs(directory_get_path(directory)); -} - -char * -map_directory_child_fs(const struct directory *directory, const char *name) -{ - assert(music_dir_utf8 != NULL); - assert(music_dir_fs != NULL); - - char *name_fs, *parent_fs, *path; - - /* check for invalid or unauthorized base names */ - if (*name == 0 || strchr(name, '/') != NULL || - strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - return NULL; - - parent_fs = map_directory_fs(directory); - if (parent_fs == NULL) - return NULL; - - name_fs = utf8_to_fs_charset(name); - if (name_fs == NULL) { - g_free(parent_fs); - return NULL; - } - - path = g_build_filename(parent_fs, name_fs, NULL); - g_free(parent_fs); - g_free(name_fs); - - return path; -} - -char * -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->uri); - else - return utf8_to_fs_charset(song->uri); -} - -char * -map_fs_to_utf8(const char *path_fs) -{ - if (music_dir_fs != NULL && - strncmp(path_fs, music_dir_fs, music_dir_fs_length) == 0 && - G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length])) - /* remove musicDir prefix */ - path_fs += music_dir_fs_length + 1; - else if (G_IS_DIR_SEPARATOR(path_fs[0])) - /* not within musicDir */ - return NULL; - - while (path_fs[0] == G_DIR_SEPARATOR) - ++path_fs; - - return fs_charset_to_utf8(path_fs); -} - -const char * -map_spl_path(void) -{ - return playlist_dir_fs; -} - -char * -map_spl_utf8_to_fs(const char *name) -{ - char *filename_utf8, *filename_fs, *path; - - if (playlist_dir_fs == NULL) - return NULL; - - filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL); - filename_fs = utf8_to_fs_charset(filename_utf8); - g_free(filename_utf8); - if (filename_fs == NULL) - return NULL; - - path = g_build_filename(playlist_dir_fs, filename_fs, NULL); - g_free(filename_fs); - - return path; -} diff --git a/src/mapper.h b/src/mapper.h deleted file mode 100644 index d6184a175..000000000 --- a/src/mapper.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Maps directory and song objects to file system paths. - */ - -#ifndef MPD_MAPPER_H -#define MPD_MAPPER_H - -#include <glib.h> -#include <stdbool.h> - -#define PLAYLIST_FILE_SUFFIX ".m3u" - -struct directory; -struct song; - -void mapper_init(const char *_music_dir, const char *_playlist_dir); - -void mapper_finish(void); - -/** - * Return the absolute path of the music directory encoded in UTF-8. - */ -G_GNUC_CONST -const char * -mapper_get_music_directory_utf8(void); - -/** - * Return the absolute path of the music directory encoded in the - * filesystem character set. - */ -G_GNUC_CONST -const char * -mapper_get_music_directory_fs(void); - -/** - * Returns true if a music directory was configured. - */ -G_GNUC_CONST -static inline bool -mapper_has_music_directory(void) -{ - return mapper_get_music_directory_utf8() != NULL; -} - -/** - * If the specified absolute path points inside the music directory, - * this function converts it to a relative path. If not, it returns - * the unmodified string pointer. - */ -G_GNUC_PURE -const char * -map_to_relative_path(const char *path_utf8); - -/** - * 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. - */ -G_GNUC_MALLOC -char * -map_uri_fs(const char *uri); - -/** - * Determines the file system path of a directory object. - * - * @param directory the directory object - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_directory_fs(const struct directory *directory); - -/** - * Determines the file system path of a directory's child (may be a - * sub directory or a song). - * - * @param directory the parent directory object - * @param name the child's name in UTF-8 - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_directory_child_fs(const struct directory *directory, const char *name); - -/** - * Determines the file system path of a song. This must not be a - * remote song. - * - * @param song the song object - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_song_fs(const struct song *song); - -/** - * Maps a file system path (relative to the music directory or - * absolute) to a relative path in UTF-8 encoding. - * - * @param path_fs a path in file system encoding - * @return the relative path in UTF-8, or NULL if mapping failed - */ -G_GNUC_MALLOC -char * -map_fs_to_utf8(const char *path_fs); - -/** - * Returns the playlist directory. - */ -G_GNUC_CONST -const char * -map_spl_path(void); - -/** - * Maps a playlist name (without the ".m3u" suffix) to a file system - * path. The return value is allocated on the heap and must be freed - * with g_free(). - * - * @return the path in file system encoding, or NULL if mapping failed - */ -G_GNUC_PURE -char * -map_spl_utf8_to_fs(const char *name); - -#endif diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx new file mode 100644 index 000000000..05d70c4c0 --- /dev/null +++ b/src/mixer/AlsaMixerPlugin.cxx @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MixerInternal.hxx" +#include "OutputAPI.hxx" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/Loop.hxx" +#include "util/ReusableArray.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <algorithm> + +#include <glib.h> +#include <alsa/asoundlib.h> + +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" +static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; + +class AlsaMixerMonitor final : private MultiSocketMonitor { + snd_mixer_t *mixer; + + ReusableArray<pollfd> pfd_buffer; + +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), mixer(_mixer) { + _loop.AddCall([this](){ InvalidateSockets(); }); + } + +private: + virtual int PrepareSockets() override; + virtual void DispatchSockets() override; +}; + +class AlsaMixer final : public Mixer { + const char *device; + const char *control; + unsigned int index; + + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long volume_min; + long volume_max; + int volume_set; + + AlsaMixerMonitor *monitor; + +public: + AlsaMixer():Mixer(alsa_mixer_plugin) {} + + void Configure(const config_param ¶m); + bool Setup(Error &error); + bool Open(Error &error); + void Close(); + + int GetVolume(Error &error); + bool SetVolume(unsigned volume, Error &error); +}; + +static constexpr Domain alsa_mixer_domain("alsa_mixer"); + +int +AlsaMixerMonitor::PrepareSockets() +{ + if (mixer == nullptr) + return -1; + + int count = snd_mixer_poll_descriptors_count(mixer); + if (count < 0) + count = 0; + + struct pollfd *pfds = pfd_buffer.Get(count); + + count = snd_mixer_poll_descriptors(mixer, pfds, count); + if (count < 0) + count = 0; + + struct pollfd *end = pfds + count; + + UpdateSocketList([pfds, end](int fd) -> unsigned { + auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ + return pfd.fd == fd; + }); + if (i == end) + return 0; + + auto events = i->events; + i->events = 0; + return events; + }); + + for (auto i = pfds; i != end; ++i) + if (i->events != 0) + AddSocket(i->fd, i->events); + + return -1; +} + +void +AlsaMixerMonitor::DispatchSockets() +{ + assert(mixer != nullptr); + + int err = snd_mixer_handle_events(mixer); + if (err < 0) { + FormatError(alsa_mixer_domain, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + + if (err == -ENODEV) { + /* the sound device was unplugged; disable + this GSource */ + mixer = nullptr; + InvalidateSockets(); + return; + } + } +} + +/* + * libasound callbacks + * + */ + +static int +alsa_mixer_elem_callback(gcc_unused snd_mixer_elem_t *elem, unsigned mask) +{ + if (mask & SND_CTL_EVENT_MASK_VALUE) + GlobalEvents::Emit(GlobalEvents::MIXER); + + return 0; +} + +/* + * mixer_plugin methods + * + */ + +inline void +AlsaMixer::Configure(const config_param ¶m) +{ + device = param.GetBlockValue("mixer_device", + VOLUME_MIXER_ALSA_DEFAULT); + control = param.GetBlockValue("mixer_control", + VOLUME_MIXER_ALSA_CONTROL_DEFAULT); + index = param.GetBlockValue("mixer_index", + VOLUME_MIXER_ALSA_INDEX_DEFAULT); +} + +static Mixer * +alsa_mixer_init(gcc_unused void *ao, const config_param ¶m, + gcc_unused Error &error) +{ + AlsaMixer *am = new AlsaMixer(); + am->Configure(param); + + return am; +} + +static void +alsa_mixer_finish(Mixer *data) +{ + AlsaMixer *am = (AlsaMixer *)data; + + delete am; + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +gcc_pure +static snd_mixer_elem_t * +alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) +{ + for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); + elem != NULL; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && + g_ascii_strcasecmp(snd_mixer_selem_get_name(elem), + name) == 0 && + snd_mixer_selem_get_index(elem) == idx) + return elem; + } + + return NULL; +} + +inline bool +AlsaMixer::Setup(Error &error) +{ + int err; + + if ((err = snd_mixer_attach(handle, device)) < 0) { + error.Format(alsa_mixer_domain, err, + "failed to attach to %s: %s", + device, snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_selem_register(handle, NULL, + NULL)) < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_selem_register() failed: %s", + snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_load(handle)) < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_load() failed: %s\n", + snd_strerror(err)); + return false; + } + + elem = alsa_mixer_lookup_elem(handle, control, index); + if (elem == NULL) { + error.Format(alsa_mixer_domain, 0, + "no such mixer control: %s", control); + return false; + } + + snd_mixer_selem_get_playback_volume_range(elem, &volume_min, + &volume_max); + + snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback); + + monitor = new AlsaMixerMonitor(*main_loop, handle); + + return true; +} + +inline bool +AlsaMixer::Open(Error &error) +{ + int err; + + volume_set = -1; + + err = snd_mixer_open(&handle, 0); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_open() failed: %s", snd_strerror(err)); + return false; + } + + if (!Setup(error)) { + snd_mixer_close(handle); + return false; + } + + return true; +} + +static bool +alsa_mixer_open(Mixer *data, Error &error) +{ + AlsaMixer *am = (AlsaMixer *)data; + + return am->Open(error); +} + +inline void +AlsaMixer::Close() +{ + assert(handle != NULL); + + delete monitor; + + snd_mixer_elem_set_callback(elem, NULL); + snd_mixer_close(handle); +} + +static void +alsa_mixer_close(Mixer *data) +{ + AlsaMixer *am = (AlsaMixer *)data; + am->Close(); +} + +inline int +AlsaMixer::GetVolume(Error &error) +{ + int err; + int ret; + long level; + + assert(handle != NULL); + + err = snd_mixer_handle_events(handle); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + return false; + } + + err = snd_mixer_selem_get_playback_volume(elem, + SND_MIXER_SCHN_FRONT_LEFT, + &level); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "failed to read ALSA volume: %s", + snd_strerror(err)); + return false; + } + + ret = ((volume_set / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5; + if (volume_set > 0 && ret == level) { + ret = volume_set; + } else { + ret = (int)(100 * (((float)(level - volume_min)) / + (volume_max - volume_min)) + 0.5); + } + + return ret; +} + +static int +alsa_mixer_get_volume(Mixer *mixer, Error &error) +{ + AlsaMixer *am = (AlsaMixer *)mixer; + return am->GetVolume(error); +} + +inline bool +AlsaMixer::SetVolume(unsigned volume, Error &error) +{ + float vol; + long level; + int err; + + assert(handle != NULL); + + vol = volume; + + volume_set = vol + 0.5; + + level = (long)(((vol / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5); + level = level > volume_max ? volume_max : level; + level = level < volume_min ? volume_min : level; + + err = snd_mixer_selem_set_playback_volume_all(elem, level); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "failed to set ALSA volume: %s", + snd_strerror(err)); + return false; + } + + return true; +} + +static bool +alsa_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + AlsaMixer *am = (AlsaMixer *)mixer; + return am->SetVolume(volume, error); +} + +const struct mixer_plugin alsa_mixer_plugin = { + alsa_mixer_init, + alsa_mixer_finish, + alsa_mixer_open, + alsa_mixer_close, + alsa_mixer_get_volume, + alsa_mixer_set_volume, + true, +}; diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx new file mode 100644 index 000000000..5b533470b --- /dev/null +++ b/src/mixer/OssMixerPlugin.cxx @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MixerInternal.hxx" +#include "OutputAPI.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +class OssMixer : public Mixer { + const char *device; + const char *control; + + int device_fd; + int volume_control; + +public: + OssMixer():Mixer(oss_mixer_plugin) {} + + bool Configure(const config_param ¶m, Error &error); + bool Open(Error &error); + void Close(); + + int GetVolume(Error &error); + bool SetVolume(unsigned volume, Error &error); +}; + +static constexpr Domain oss_mixer_domain("oss_mixer"); + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +inline bool +OssMixer::Configure(const config_param ¶m, Error &error) +{ + device = param.GetBlockValue("mixer_device", VOLUME_MIXER_OSS_DEFAULT); + control = param.GetBlockValue("mixer_control"); + + if (control != NULL) { + volume_control = oss_find_mixer(control); + if (volume_control < 0) { + error.Format(oss_mixer_domain, 0, + "no such mixer control: %s", control); + return false; + } + } else + volume_control = SOUND_MIXER_PCM; + + return true; +} + +static Mixer * +oss_mixer_init(gcc_unused void *ao, const config_param ¶m, + Error &error) +{ + OssMixer *om = new OssMixer(); + + if (!om->Configure(param, error)) { + delete om; + return nullptr; + } + + return om; +} + +static void +oss_mixer_finish(Mixer *data) +{ + OssMixer *om = (OssMixer *) data; + + delete om; +} + +void +OssMixer::Close() +{ + assert(device_fd >= 0); + + close(device_fd); +} + +static void +oss_mixer_close(Mixer *data) +{ + OssMixer *om = (OssMixer *) data; + om->Close(); +} + +inline bool +OssMixer::Open(Error &error) +{ + device_fd = open_cloexec(device, O_RDONLY, 0); + if (device_fd < 0) { + error.FormatErrno("failed to open %s", device); + return false; + } + + if (control) { + int devmask = 0; + + if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + error.SetErrno("READ_DEVMASK failed"); + Close(); + return false; + } + + if (((1 << volume_control) & devmask) == 0) { + error.Format(oss_mixer_domain, 0, + "mixer control \"%s\" not usable", + control); + Close(); + return false; + } + } + + return true; +} + +static bool +oss_mixer_open(Mixer *data, Error &error) +{ + OssMixer *om = (OssMixer *) data; + + return om->Open(error); +} + +inline int +OssMixer::GetVolume(Error &error) +{ + int left, right, level; + int ret; + + assert(device_fd >= 0); + + ret = ioctl(device_fd, MIXER_READ(volume_control), &level); + if (ret < 0) { + error.SetErrno("failed to read OSS volume"); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + FormatWarning(oss_mixer_domain, + "volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +static int +oss_mixer_get_volume(Mixer *mixer, Error &error) +{ + OssMixer *om = (OssMixer *)mixer; + return om->GetVolume(error); +} + +inline bool +OssMixer::SetVolume(unsigned volume, Error &error) +{ + int level; + int ret; + + assert(device_fd >= 0); + assert(volume <= 100); + + level = (volume << 8) + volume; + + ret = ioctl(device_fd, MIXER_WRITE(volume_control), &level); + if (ret < 0) { + error.SetErrno("failed to set OSS volume"); + return false; + } + + return true; +} + +static bool +oss_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + OssMixer *om = (OssMixer *)mixer; + return om->SetVolume(volume, error); +} + +const struct mixer_plugin oss_mixer_plugin = { + oss_mixer_init, + oss_mixer_finish, + oss_mixer_open, + oss_mixer_close, + oss_mixer_get_volume, + oss_mixer_set_volume, + true, +}; diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx new file mode 100644 index 000000000..f651c34b0 --- /dev/null +++ b/src/mixer/PulseMixerPlugin.cxx @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PulseMixerPlugin.hxx" +#include "MixerInternal.hxx" +#include "output/PulseOutputPlugin.hxx" +#include "GlobalEvents.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#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> + +struct PulseMixer final : public Mixer { + PulseOutput *output; + + bool online; + struct pa_cvolume volume; + + PulseMixer(PulseOutput *_output) + :Mixer(pulse_mixer_plugin), + output(_output), online(false) + { + } +}; + +static constexpr Domain pulse_mixer_domain("pulse_mixer"); + +static void +pulse_mixer_offline(PulseMixer *pm) +{ + if (!pm->online) + return; + + pm->online = false; + + GlobalEvents::Emit(GlobalEvents::MIXER); +} + +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ +static void +pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) +{ + PulseMixer *pm = (PulseMixer *)userdata; + + if (eol) + return; + + if (i == NULL) { + pulse_mixer_offline(pm); + return; + } + + pm->online = true; + pm->volume = i->volume; + + GlobalEvents::Emit(GlobalEvents::MIXER); +} + +static void +pulse_mixer_update(PulseMixer *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) { + FormatError(pulse_mixer_domain, + "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(gcc_unused PulseMixer *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) { + FormatError(pulse_mixer_domain, + "pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_disconnect(PulseMixer *pm) +{ + pulse_mixer_offline(pm); +} + +void +pulse_mixer_on_change(PulseMixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pulse_mixer_update(pm, context, stream); +} + +static Mixer * +pulse_mixer_init(void *ao, gcc_unused const config_param ¶m, + Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + + if (ao == NULL) { + error.Set(pulse_mixer_domain, + "The pulse mixer cannot work without the audio output"); + return nullptr; + } + + PulseMixer *pm = new PulseMixer(po); + + pulse_output_set_mixer(po, pm); + + return pm; +} + +static void +pulse_mixer_finish(Mixer *data) +{ + PulseMixer *pm = (PulseMixer *) data; + + pulse_output_clear_mixer(pm->output, pm); + + delete pm; +} + +static int +pulse_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) +{ + PulseMixer *pm = (PulseMixer *) mixer; + int ret; + + pulse_output_lock(pm->output); + + ret = pm->online + ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) + : -1; + + pulse_output_unlock(pm->output); + + return ret; +} + +static bool +pulse_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + PulseMixer *pm = (PulseMixer *) mixer; + struct pa_cvolume cvolume; + bool success; + + pulse_output_lock(pm->output); + + if (!pm->online) { + pulse_output_unlock(pm->output); + error.Set(pulse_mixer_domain, "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); + if (success) + pm->volume = cvolume; + + pulse_output_unlock(pm->output); + + return success; +} + +const struct mixer_plugin pulse_mixer_plugin = { + pulse_mixer_init, + pulse_mixer_finish, + nullptr, + nullptr, + pulse_mixer_get_volume, + pulse_mixer_set_volume, + false, +}; diff --git a/src/mixer/PulseMixerPlugin.hxx b/src/mixer/PulseMixerPlugin.hxx new file mode 100644 index 000000000..fa73e0f5e --- /dev/null +++ b/src/mixer/PulseMixerPlugin.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PULSE_MIXER_PLUGIN_HXX + +#include <pulse/def.h> + +struct PulseMixer; +struct pa_context; +struct pa_stream; + +void +pulse_mixer_on_connect(PulseMixer *pm, struct pa_context *context); + +void +pulse_mixer_on_disconnect(PulseMixer *pm); + +void +pulse_mixer_on_change(PulseMixer *pm, + struct pa_context *context, struct pa_stream *stream); + +#endif diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx new file mode 100644 index 000000000..6bd700551 --- /dev/null +++ b/src/mixer/RoarMixerPlugin.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include "config.h" +#include "MixerInternal.hxx" +#include "OutputAPI.hxx" +#include "output/RoarOutputPlugin.hxx" + +struct RoarMixer final : public Mixer { + /** the base mixer class */ + RoarOutput *self; + + RoarMixer(RoarOutput *_output) + :Mixer(roar_mixer_plugin), + self(_output) {} +}; + +static Mixer * +roar_mixer_init(void *ao, gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new RoarMixer((RoarOutput *)ao); +} + +static void +roar_mixer_finish(Mixer *data) +{ + RoarMixer *self = (RoarMixer *) data; + + delete self; +} + +static int +roar_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) +{ + RoarMixer *self = (RoarMixer *)mixer; + return roar_output_get_volume(self->self); +} + +static bool +roar_mixer_set_volume(Mixer *mixer, unsigned volume, + gcc_unused Error &error) +{ + RoarMixer *self = (RoarMixer *)mixer; + return roar_output_set_volume(self->self, volume); +} + +const struct mixer_plugin roar_mixer_plugin = { + roar_mixer_init, + roar_mixer_finish, + nullptr, + nullptr, + roar_mixer_get_volume, + roar_mixer_set_volume, + false, +}; diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx new file mode 100644 index 000000000..b7eb8ff0f --- /dev/null +++ b/src/mixer/SoftwareMixerPlugin.cxx @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SoftwareMixerPlugin.hxx" +#include "MixerInternal.hxx" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "FilterInternal.hxx" +#include "filter/VolumeFilterPlugin.hxx" +#include "pcm/PcmVolume.hxx" +#include "ConfigData.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <math.h> + +static Filter * +CreateVolumeFilter() +{ + Error error; + return filter_new(&volume_filter_plugin, config_param(), error); +} + +struct SoftwareMixer final : public Mixer { + Filter *filter; + + unsigned volume; + + SoftwareMixer() + :Mixer(software_mixer_plugin), + filter(CreateVolumeFilter()), + volume(100) + { + assert(filter != nullptr); + } + + ~SoftwareMixer() { + delete filter; + } +}; + +static Mixer * +software_mixer_init(gcc_unused void *ao, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new SoftwareMixer(); +} + +static void +software_mixer_finish(Mixer *data) +{ + SoftwareMixer *sm = (SoftwareMixer *)data; + + delete sm; +} + +static int +software_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) +{ + SoftwareMixer *sm = (SoftwareMixer *)mixer; + + return sm->volume; +} + +static bool +software_mixer_set_volume(Mixer *mixer, unsigned volume, + gcc_unused Error &error) +{ + SoftwareMixer *sm = (SoftwareMixer *)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 = { + software_mixer_init, + software_mixer_finish, + nullptr, + nullptr, + software_mixer_get_volume, + software_mixer_set_volume, + true, +}; + +Filter * +software_mixer_get_filter(Mixer *mixer) +{ + SoftwareMixer *sm = (SoftwareMixer *)mixer; + assert(sm->IsPlugin(software_mixer_plugin)); + + return sm->filter; +} diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx new file mode 100644 index 000000000..be59c08db --- /dev/null +++ b/src/mixer/SoftwareMixerPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOFTWARE_MIXER_PLUGIN_HXX +#define MPD_SOFTWARE_MIXER_PLUGIN_HXX + +class Mixer; +class Filter; + +/** + * Returns the (volume) filter associated with this mixer. All users + * of this mixer plugin should install this filter. + */ +Filter * +software_mixer_get_filter(Mixer *mixer); + +#endif diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx new file mode 100644 index 000000000..dbb43dce4 --- /dev/null +++ b/src/mixer/WinmmMixerPlugin.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "MixerInternal.hxx" +#include "OutputAPI.hxx" +#include "output/WinmmOutputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <mmsystem.h> + +#include <assert.h> +#include <math.h> +#include <windows.h> + +struct WinmmMixer final : public Mixer { + WinmmOutput *output; + + WinmmMixer(WinmmOutput *_output) + :Mixer(winmm_mixer_plugin), + output(_output) { + } +}; + +static constexpr Domain winmm_mixer_domain("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 Mixer * +winmm_mixer_init(void *ao, gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + assert(ao != nullptr); + + return new WinmmMixer((WinmmOutput *)ao); +} + +static void +winmm_mixer_finish(Mixer *data) +{ + WinmmMixer *wm = (WinmmMixer *)data; + + delete wm; +} + +static int +winmm_mixer_get_volume(Mixer *mixer, Error &error) +{ + WinmmMixer *wm = (WinmmMixer *) mixer; + DWORD volume; + HWAVEOUT handle = winmm_output_get_handle(wm->output); + MMRESULT result = waveOutGetVolume(handle, &volume); + + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_mixer_domain, "Failed to get winmm volume"); + return -1; + } + + return winmm_volume_decode(volume); +} + +static bool +winmm_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + WinmmMixer *wm = (WinmmMixer *) mixer; + DWORD value = winmm_volume_encode(volume); + HWAVEOUT handle = winmm_output_get_handle(wm->output); + MMRESULT result = waveOutSetVolume(handle, value); + + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_mixer_domain, "Failed to set winmm volume"); + return false; + } + + return true; +} + +const struct mixer_plugin winmm_mixer_plugin = { + winmm_mixer_init, + winmm_mixer_finish, + nullptr, + nullptr, + winmm_mixer_get_volume, + winmm_mixer_set_volume, + false, +}; diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c deleted file mode 100644 index 0c8625cc1..000000000 --- a/src/mixer/alsa_mixer_plugin.c +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_api.h" -#include "output_api.h" -#include "event_pipe.h" - -#include <glib.h> -#include <alsa/asoundlib.h> - -#define VOLUME_MIXER_ALSA_DEFAULT "default" -#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" -#define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0 - -struct alsa_mixer_source { - GSource source; - - snd_mixer_t *mixer; - - /** a linked list of all registered GPollFD objects */ - GSList *fds; -}; - -struct alsa_mixer { - /** the base mixer class */ - struct mixer base; - - const char *device; - const char *control; - unsigned int index; - - snd_mixer_t *handle; - snd_mixer_elem_t *elem; - long volume_min; - long volume_max; - int volume_set; - - struct alsa_mixer_source *source; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -alsa_mixer_quark(void) -{ - return g_quark_from_static_string("alsa_mixer"); -} - -/* - * GSource helper functions - * - */ - -static GSList ** -find_fd(GSList **list_r, int fd) -{ - while (true) { - GSList *list = *list_r; - if (list == NULL) - return NULL; - - GPollFD *p = list->data; - if (p->fd == fd) - return list_r; - - list_r = &list->next; - } -} - -static void -alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p, - GSList **old_r) -{ - GSList **found_r = find_fd(old_r, p->fd); - if (found_r == NULL) { - /* new fd */ - GPollFD *q = g_new(GPollFD, 1); - q->fd = p->fd; - q->events = p->events; - g_source_add_poll(&source->source, q); - source->fds = g_slist_prepend(source->fds, q); - return; - } - - GSList *found = *found_r; - *found_r = found->next; - - GPollFD *q = found->data; - if (q->events != p->events) { - /* refresh events */ - g_source_remove_poll(&source->source, q); - q->events = p->events; - g_source_add_poll(&source->source, q); - } - - found->next = source->fds; - source->fds = found; -} - -static void -alsa_mixer_update_fds(struct alsa_mixer_source *source) -{ - int count = snd_mixer_poll_descriptors_count(source->mixer); - if (count < 0) - count = 0; - - struct pollfd *pfds = g_new(struct pollfd, count); - count = snd_mixer_poll_descriptors(source->mixer, pfds, count); - if (count < 0) - count = 0; - - GSList *old = source->fds; - source->fds = NULL; - - for (int i = 0; i < count; ++i) - alsa_mixer_update_fd(source, &pfds[i], &old); - g_free(pfds); - - for (; old != NULL; old = old->next) { - GPollFD *q = old->data; - g_source_remove_poll(&source->source, q); - g_free(q); - } - - g_slist_free(old); -} - -/* - * GSource methods - * - */ - -static gboolean -alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - alsa_mixer_update_fds(source); - - return false; -} - -static gboolean -alsa_mixer_source_check(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (const GSList *i = source->fds; i != NULL; i = i->next) { - const GPollFD *poll_fd = i->data; - if (poll_fd->revents != 0) - return true; - } - - return false; -} - -static gboolean -alsa_mixer_source_dispatch(GSource *_source, - G_GNUC_UNUSED GSourceFunc callback, - G_GNUC_UNUSED gpointer user_data) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - int err = snd_mixer_handle_events(source->mixer); - if (err < 0) { - g_warning("snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - - if (err == -ENODEV) - /* the sound device was unplugged; disable - this GSource */ - return false; - } - - return true; -} - -static void -alsa_mixer_source_finalize(GSource *_source) -{ - struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; - - for (GSList *i = source->fds; i != NULL; i = i->next) - g_free(i->data); - - g_slist_free(source->fds); -} - -static GSourceFuncs alsa_mixer_source_funcs = { - .prepare = alsa_mixer_source_prepare, - .check = alsa_mixer_source_check, - .dispatch = alsa_mixer_source_dispatch, - .finalize = alsa_mixer_source_finalize, -}; - -/* - * libasound callbacks - * - */ - -static int -alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask) -{ - if (mask & SND_CTL_EVENT_MASK_VALUE) - event_pipe_emit(PIPE_EVENT_MIXER); - - return 0; -} - -/* - * mixer_plugin methods - * - */ - -static struct mixer * -alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct alsa_mixer *am = g_new(struct alsa_mixer, 1); - - mixer_init(&am->base, &alsa_mixer_plugin); - - am->device = config_get_block_string(param, "mixer_device", - VOLUME_MIXER_ALSA_DEFAULT); - am->control = config_get_block_string(param, "mixer_control", - VOLUME_MIXER_ALSA_CONTROL_DEFAULT); - am->index = config_get_block_unsigned(param, "mixer_index", - VOLUME_MIXER_ALSA_INDEX_DEFAULT); - - return &am->base; -} - -static void -alsa_mixer_finish(struct mixer *data) -{ - struct alsa_mixer *am = (struct alsa_mixer *)data; - - g_free(am); - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -G_GNUC_PURE -static snd_mixer_elem_t * -alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) -{ - for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); - elem != NULL; elem = snd_mixer_elem_next(elem)) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && - g_ascii_strcasecmp(snd_mixer_selem_get_name(elem), - name) == 0 && - snd_mixer_selem_get_index(elem) == idx) - return elem; - } - - return NULL; -} - -static bool -alsa_mixer_setup(struct alsa_mixer *am, GError **error_r) -{ - int err; - - if ((err = snd_mixer_attach(am->handle, am->device)) < 0) { - 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_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_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_load() failed: %s\n", - snd_strerror(err)); - return false; - } - - am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index); - if (am->elem == NULL) { - g_set_error(error_r, alsa_mixer_quark(), 0, - "no such mixer control: %s", am->control); - return false; - } - - snd_mixer_selem_get_playback_volume_range(am->elem, - &am->volume_min, - &am->volume_max); - - snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback); - - am->source = (struct alsa_mixer_source *) - g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source)); - am->source->mixer = am->handle; - am->source->fds = NULL; - g_source_attach(&am->source->source, g_main_context_default()); - - return true; -} - -static bool -alsa_mixer_open(struct mixer *data, GError **error_r) -{ - struct alsa_mixer *am = (struct alsa_mixer *)data; - int err; - - am->volume_set = -1; - - err = snd_mixer_open(&am->handle, 0); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_open() failed: %s", snd_strerror(err)); - return false; - } - - if (!alsa_mixer_setup(am, error_r)) { - snd_mixer_close(am->handle); - return false; - } - - return true; -} - -static void -alsa_mixer_close(struct mixer *data) -{ - struct alsa_mixer *am = (struct alsa_mixer *)data; - - assert(am->handle != NULL); - - g_source_destroy(&am->source->source); - g_source_unref(&am->source->source); - - snd_mixer_elem_set_callback(am->elem, NULL); - snd_mixer_close(am->handle); -} - -static int -alsa_mixer_get_volume(struct mixer *mixer, GError **error_r) -{ - struct alsa_mixer *am = (struct alsa_mixer *)mixer; - int err; - int ret; - long level; - - assert(am->handle != NULL); - - err = snd_mixer_handle_events(am->handle); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - return false; - } - - err = snd_mixer_selem_get_playback_volume(am->elem, - SND_MIXER_SCHN_FRONT_LEFT, - &level); - if (err < 0) { - g_set_error(error_r, alsa_mixer_quark(), err, - "failed to read ALSA volume: %s", - snd_strerror(err)); - return false; - } - - ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min) - + am->volume_min) + 0.5; - if (am->volume_set > 0 && ret == level) { - ret = am->volume_set; - } else { - ret = (int)(100 * (((float)(level - am->volume_min)) / - (am->volume_max - am->volume_min)) + 0.5); - } - - return ret; -} - -static bool -alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct alsa_mixer *am = (struct alsa_mixer *)mixer; - float vol; - long level; - int err; - - assert(am->handle != NULL); - - vol = volume; - - am->volume_set = vol + 0.5; - - level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) + - am->volume_min) + 0.5); - level = level > am->volume_max ? am->volume_max : level; - level = level < am->volume_min ? am->volume_min : level; - - err = snd_mixer_selem_set_playback_volume_all(am->elem, level); - if (err < 0) { - 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_plugin = { - .init = alsa_mixer_init, - .finish = alsa_mixer_finish, - .open = alsa_mixer_open, - .close = alsa_mixer_close, - .get_volume = alsa_mixer_get_volume, - .set_volume = alsa_mixer_set_volume, - .global = true, -}; diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c deleted file mode 100644 index 608f1f9b8..000000000 --- a/src/mixer/oss_mixer_plugin.c +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_api.h" -#include "output_api.h" -#include "fd_util.h" - -#include <glib.h> - -#include <assert.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" - -struct oss_mixer { - /** the base mixer class */ - struct mixer base; - - const char *device; - const char *control; - - int device_fd; - 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) -{ - const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; - size_t name_length = strlen(name); - - for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 && - (labels[i][name_length] == 0 || - labels[i][name_length] == ' ')) - return i; - } - return -1; -} - -static struct mixer * -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_plugin); - - om->device = config_get_block_string(param, "mixer_device", - VOLUME_MIXER_OSS_DEFAULT); - om->control = config_get_block_string(param, "mixer_control", NULL); - - if (om->control != NULL) { - om->volume_control = oss_find_mixer(om->control); - if (om->volume_control < 0) { - g_free(om); - g_set_error(error_r, oss_mixer_quark(), 0, - "no such mixer control: %s", om->control); - return NULL; - } - } else - om->volume_control = SOUND_MIXER_PCM; - - return &om->base; -} - -static void -oss_mixer_finish(struct mixer *data) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - g_free(om); -} - -static void -oss_mixer_close(struct mixer *data) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - assert(om->device_fd >= 0); - - close(om->device_fd); -} - -static bool -oss_mixer_open(struct mixer *data, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - om->device_fd = open_cloexec(om->device, O_RDONLY, 0); - if (om->device_fd < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to open %s: %s", - om->device, g_strerror(errno)); - return false; - } - - if (om->control) { - int devmask = 0; - - if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - 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_set_error(error_r, oss_mixer_quark(), 0, - "mixer control \"%s\" not usable", - om->control); - oss_mixer_close(data); - return false; - } - } - return true; -} - -static int -oss_mixer_get_volume(struct mixer *mixer, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *)mixer; - int left, right, level; - int ret; - - assert(om->device_fd >= 0); - - ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); - if (ret < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to read OSS volume: %s", - g_strerror(errno)); - return false; - } - - left = level & 0xff; - right = (level & 0xff00) >> 8; - - if (left != right) { - g_warning("volume for left and right is not the same, \"%i\" and " - "\"%i\"\n", left, right); - } - - return left; -} - -static bool -oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *)mixer; - int level; - int ret; - - assert(om->device_fd >= 0); - assert(volume <= 100); - - level = (volume << 8) + volume; - - ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); - if (ret < 0) { - 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_plugin = { - .init = oss_mixer_init, - .finish = oss_mixer_finish, - .open = oss_mixer_open, - .close = oss_mixer_close, - .get_volume = oss_mixer_get_volume, - .set_volume = oss_mixer_set_volume, - .global = true, -}; diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c deleted file mode 100644 index a82c032b3..000000000 --- a/src/mixer/pulse_mixer_plugin.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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; - - pulse_output_lock(pm->output); - - ret = pm->online - ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) - : -1; - - pulse_output_unlock(pm->output); - - 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; - - pulse_output_lock(pm->output); - - if (!pm->online) { - pulse_output_unlock(pm->output); - 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; - - pulse_output_unlock(pm->output); - - 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 deleted file mode 100644 index 461633d37..000000000 --- a/src/mixer/pulse_mixer_plugin.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c deleted file mode 100644 index 47d3c17f9..000000000 --- a/src/mixer/roar_mixer_plugin.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - - -#include "config.h" -#include "mixer_api.h" -#include "output_api.h" -#include "output/roar_output_plugin.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <unistd.h> - -typedef struct roar_mpd_mixer -{ - /** the base mixer class */ - struct mixer base; - struct roar *self; -} roar_mixer_t; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -roar_mixer_quark(void) -{ - return g_quark_from_static_string("roar_mixer"); -} - -static struct mixer * -roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - roar_mixer_t *self = g_new(roar_mixer_t, 1); - self->self = ao; - - mixer_init(&self->base, &roar_mixer_plugin); - - return &self->base; -} - -static void -roar_mixer_finish(struct mixer *data) -{ - roar_mixer_t *self = (roar_mixer_t *) data; - - g_free(self); -} - -static void -roar_mixer_close(G_GNUC_UNUSED struct mixer *data) -{ -} - -static bool -roar_mixer_open(G_GNUC_UNUSED struct mixer *data, - G_GNUC_UNUSED GError **error_r) -{ - return true; -} - -static int -roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) -{ - roar_mixer_t *self = (roar_mixer_t *)mixer; - return roar_output_get_volume(self->self); -} - -static bool -roar_mixer_set_volume(struct mixer *mixer, unsigned volume, - G_GNUC_UNUSED GError **error_r) -{ - roar_mixer_t *self = (roar_mixer_t *)mixer; - return roar_output_set_volume(self->self, volume); -} - -const struct mixer_plugin roar_mixer_plugin = { - .init = roar_mixer_init, - .finish = roar_mixer_finish, - .open = roar_mixer_open, - .close = roar_mixer_close, - .get_volume = roar_mixer_get_volume, - .set_volume = roar_mixer_set_volume, - .global = false, -}; diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c deleted file mode 100644 index 0206c3b99..000000000 --- a/src/mixer/software_mixer_plugin.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index ee2b2023c..000000000 --- a/src/mixer/software_mixer_plugin.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef 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 deleted file mode 100644 index ceddf6afd..000000000 --- a/src/mixer/winmm_mixer_plugin.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 95ba90793..000000000 --- a/src/mixer_all.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mixer" - -static int -output_mixer_get_volume(unsigned i) -{ - struct audio_output *output; - struct mixer *mixer; - int volume; - GError *error = NULL; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) - return -1; - - mixer = output->mixer; - if (mixer == NULL) - return -1; - - 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 -mixer_all_get_volume(void) -{ - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; - - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_volume(i); - if (volume >= 0) { - total += volume; - ++ok; - } - } - - if (ok == 0) - return -1; - - return total / ok; -} - -static bool -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) - return false; - - mixer = output->mixer; - if (mixer == NULL) - return false; - - 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); - } - - return success; -} - -bool -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) - || 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 deleted file mode 100644 index fe873e713..000000000 --- a/src/mixer_all.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Functions which affect the mixers of all audio outputs. - */ - -#ifndef MPD_MIXER_ALL_H -#define MPD_MIXER_ALL_H - -#include <stdbool.h> - -/** - * Returns the average volume of all available mixers (range 0..100). - * Returns -1 if no mixer can be queried. - */ -int -mixer_all_get_volume(void); - -/** - * Sets the volume on all available mixers. - * - * @param volume the volume (range 0..100) - * @return true on success, false on failure - */ -bool -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 deleted file mode 100644 index c85916c94..000000000 --- a/src/mixer_api.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_api.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mixer" - -void -mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin) -{ - mixer->plugin = plugin; - mixer->mutex = g_mutex_new(); - mixer->open = false; - mixer->failed = false; -} diff --git a/src/mixer_api.h b/src/mixer_api.h deleted file mode 100644 index 29c1e00ca..000000000 --- a/src/mixer_api.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MIXER_H -#define MPD_MIXER_H - -#include "mixer_plugin.h" -#include "mixer_list.h" - -#include <glib.h> - -struct mixer { - const struct mixer_plugin *plugin; - - /** - * This mutex protects all of the mixer struct, including its - * implementation, so plugins don't have to deal with that. - */ - GMutex *mutex; - - /** - * Is the mixer device currently open? - */ - bool open; - - /** - * Has this mixer failed, and should not be reopened - * automatically? - */ - bool failed; -}; - -void -mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin); - -#endif diff --git a/src/mixer_control.c b/src/mixer_control.c deleted file mode 100644 index 3e984dd04..000000000 --- a/src/mixer_control.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_control.h" -#include "mixer_api.h" - -#include <assert.h> -#include <stddef.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mixer" - -struct mixer * -mixer_new(const struct mixer_plugin *plugin, void *ao, - const struct config_param *param, - GError **error_r) -{ - struct mixer *mixer; - - assert(plugin != NULL); - - mixer = plugin->init(ao, param, error_r); - - assert(mixer == NULL || mixer->plugin == plugin); - - return mixer; -} - -void -mixer_free(struct mixer *mixer) -{ - assert(mixer != NULL); - assert(mixer->plugin != NULL); - assert(mixer->mutex != NULL); - - /* mixers with the "global" flag set might still be open at - this point (see mixer_auto_close()) */ - mixer_close(mixer); - - g_mutex_free(mixer->mutex); - - mixer->plugin->finish(mixer); -} - -bool -mixer_open(struct mixer *mixer, GError **error_r) -{ - bool success; - - assert(mixer != NULL); - assert(mixer->plugin != NULL); - - g_mutex_lock(mixer->mutex); - - if (mixer->open) - success = true; - else if (mixer->plugin->open == NULL) - success = mixer->open = true; - else - success = mixer->open = mixer->plugin->open(mixer, error_r); - - mixer->failed = !success; - - g_mutex_unlock(mixer->mutex); - - return success; -} - -static void -mixer_close_internal(struct mixer *mixer) -{ - assert(mixer != NULL); - assert(mixer->plugin != NULL); - assert(mixer->open); - - if (mixer->plugin->close != NULL) - mixer->plugin->close(mixer); - - mixer->open = false; -} - -void -mixer_close(struct mixer *mixer) -{ - assert(mixer != NULL); - assert(mixer->plugin != NULL); - - g_mutex_lock(mixer->mutex); - - if (mixer->open) - mixer_close_internal(mixer); - - g_mutex_unlock(mixer->mutex); -} - -void -mixer_auto_close(struct mixer *mixer) -{ - if (!mixer->plugin->global) - mixer_close(mixer); -} - -/* - * Close the mixer due to failure. The mutex must be locked before - * calling this function. - */ -static void -mixer_failed(struct mixer *mixer) -{ - assert(mixer->open); - - mixer_close_internal(mixer); - - mixer->failed = true; -} - -int -mixer_get_volume(struct mixer *mixer, GError **error_r) -{ - int volume; - - assert(mixer != NULL); - - if (mixer->plugin->global && !mixer->failed && - !mixer_open(mixer, error_r)) - return -1; - - g_mutex_lock(mixer->mutex); - - if (mixer->open) { - 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; - - g_mutex_unlock(mixer->mutex); - - return volume; -} - -bool -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, error_r)) - return false; - - g_mutex_lock(mixer->mutex); - - if (mixer->open) { - success = mixer->plugin->set_volume(mixer, volume, error_r); - } else - success = false; - - g_mutex_unlock(mixer->mutex); - - return success; -} diff --git a/src/mixer_control.h b/src/mixer_control.h deleted file mode 100644 index 6c3468aca..000000000 --- a/src/mixer_control.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Functions which manipulate a #mixer object. - */ - -#ifndef MPD_MIXER_CONTROL_H -#define MPD_MIXER_CONTROL_H - -#include <glib.h> - -#include <stdbool.h> - -struct mixer; -struct mixer_plugin; -struct config_param; - -struct mixer * -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, GError **error_r); - -void -mixer_close(struct mixer *mixer); - -/** - * Close the mixer unless the plugin's "global" flag is set. This is - * called when the #audio_output is closed. - */ -void -mixer_auto_close(struct mixer *mixer); - -int -mixer_get_volume(struct mixer *mixer, GError **error_r); - -bool -mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r); - -#endif diff --git a/src/mixer_list.h b/src/mixer_list.h deleted file mode 100644 index 078358ec3..000000000 --- a/src/mixer_list.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header provides "extern" declarations for all mixer plugins. - */ - -#ifndef MPD_MIXER_LIST_H -#define MPD_MIXER_LIST_H - -extern const struct mixer_plugin software_mixer_plugin; -extern const struct mixer_plugin alsa_mixer_plugin; -extern const struct mixer_plugin oss_mixer_plugin; -extern const struct mixer_plugin roar_mixer_plugin; -extern const struct mixer_plugin pulse_mixer_plugin; -extern const struct mixer_plugin winmm_mixer_plugin; - -#endif diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h deleted file mode 100644 index 9532b95cb..000000000 --- a/src/mixer_plugin.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the mixer_plugin class. It should not be - * included directly; use mixer_api.h instead in mixer - * implementations. - */ - -#ifndef MPD_MIXER_PLUGIN_H -#define MPD_MIXER_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> - -struct config_param; -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 occurring, or - * NULL to ignore errors - * @return a mixer object, or NULL on error - */ - struct mixer *(*init)(void *ao, const struct config_param *param, - GError **error_r); - - /** - * Finish and free mixer data - */ - void (*finish)(struct mixer *data); - - /** - * Open mixer device - * - * @param error_r location to store the error occurring, or - * NULL to ignore errors - * @return true on success, false on error - */ - bool (*open)(struct mixer *data, GError **error_r); - - /** - * Close mixer device - */ - void (*close)(struct mixer *data); - - /** - * Reads the current volume. - * - * @param error_r location to store the error occurring, or - * NULL to ignore errors - * @return the current volume (0..100 including) or -1 if - * unavailable or on error (error_r set, mixer will be closed) - */ - int (*get_volume)(struct mixer *mixer, GError **error_r); - - /** - * Sets the volume. - * - * @param error_r location to store the error occurring, or - * NULL to ignore errors - * @param volume the new volume (0..100 including) - * @return true on success, false on error - */ - bool (*set_volume)(struct mixer *mixer, unsigned volume, - GError **error_r); - - /** - * If true, then the mixer is automatically opened, even if - * its audio output is not open. If false, then the mixer is - * disabled as long as its audio output is closed. - */ - bool global; -}; - -#endif diff --git a/src/mixer_type.c b/src/mixer_type.c deleted file mode 100644 index a479caf16..000000000 --- a/src/mixer_type.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 15d136b5b..000000000 --- a/src/mixer_type.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index 219738ced..000000000 --- a/src/mpd_error.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index 3c0112c91..000000000 --- a/src/notify.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "notify.h" - -void notify_init(struct notify *notify) -{ - notify->mutex = g_mutex_new(); - notify->cond = g_cond_new(); - notify->pending = false; -} - -void notify_deinit(struct notify *notify) -{ - g_mutex_free(notify->mutex); - g_cond_free(notify->cond); -} - -void notify_wait(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - while (!notify->pending) - g_cond_wait(notify->cond, notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} - -void notify_signal(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = true; - g_cond_signal(notify->cond); - g_mutex_unlock(notify->mutex); -} - -void notify_clear(struct notify *notify) -{ - g_mutex_lock(notify->mutex); - notify->pending = false; - g_mutex_unlock(notify->mutex); -} diff --git a/src/notify.cxx b/src/notify.cxx new file mode 100644 index 000000000..64018968c --- /dev/null +++ b/src/notify.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "notify.hxx" + +void +notify::Wait() +{ + const ScopeLock protect(mutex); + while (!pending) + cond.wait(mutex); + pending = false; +} + +void +notify::Signal() +{ + const ScopeLock protect(mutex); + pending = true; + cond.signal(); +} + +void +notify::Clear() +{ + const ScopeLock protect(mutex); + pending = false; +} diff --git a/src/notify.h b/src/notify.h deleted file mode 100644 index 40821690c..000000000 --- a/src/notify.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_NOTIFY_H -#define MPD_NOTIFY_H - -#include <glib.h> - -#include <stdbool.h> - -struct notify { - GMutex *mutex; - GCond *cond; - bool pending; -}; - -void notify_init(struct notify *notify); - -void notify_deinit(struct notify *notify); - -/** - * Wait for a notification. Return immediately if we have already - * been notified since we last returned from notify_wait(). - */ -void notify_wait(struct notify *notify); - -/** - * Notify the thread. This function never blocks. - */ -void notify_signal(struct notify *notify); - -/** - * Clears a pending notification. - */ -void notify_clear(struct notify *notify); - -#endif diff --git a/src/notify.hxx b/src/notify.hxx new file mode 100644 index 000000000..6b9e95368 --- /dev/null +++ b/src/notify.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_NOTIFY_HXX +#define MPD_NOTIFY_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct notify { + Mutex mutex; + Cond cond; + bool pending; + +#ifndef WIN32 + constexpr +#endif + notify():pending(false) {} + + /** + * Wait for a notification. Return immediately if we have already + * been notified since we last returned from notify_wait(). + */ + void Wait(); + + /** + * Notify the thread. This function never blocks. + */ + void Signal(); + + /** + * Clears a pending notification. + */ + void Clear(); +}; + +#endif diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx new file mode 100644 index 000000000..79b81282b --- /dev/null +++ b/src/output/AlsaOutputPlugin.cxx @@ -0,0 +1,853 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AlsaOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <alsa/asoundlib.h> + +#include <string> + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +static const char default_device[] = "default"; + +static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000; + +#define MPD_ALSA_RETRY_NR 5 + +typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, + snd_pcm_uframes_t size); + +struct AlsaOutput { + struct audio_output base; + + Manual<PcmExport> pcm_export; + + /** + * The configured name of the ALSA device; empty for the + * default device + */ + std::string device; + + /** use memory mapped I/O? */ + bool use_mmap; + + /** + * Enable DSD over USB according to the dCS suggested + * standard? + * + * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + */ + bool dsd_usb; + + /** libasound's buffer_time setting (in microseconds) */ + unsigned int buffer_time; + + /** libasound's period_time setting (in microseconds) */ + unsigned int period_time; + + /** the mode flags passed to snd_pcm_open */ + int mode; + + /** the libasound PCM device handle */ + snd_pcm_t *pcm; + + /** + * a pointer to the libasound writei() function, which is + * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the + * use_mmap configuration + */ + alsa_writei_t *writei; + + /** + * The size of one audio frame passed to method play(). + */ + size_t in_frame_size; + + /** + * The size of one audio frame passed to libasound. + */ + size_t out_frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; + + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + void *silence; + + AlsaOutput():mode(0), writei(snd_pcm_writei) { + } + + bool Init(const config_param ¶m, Error &error) { + return ao_base_init(&base, &alsa_output_plugin, + param, error); + } + + void Deinit() { + ao_base_finish(&base); + } +}; + +static constexpr Domain alsa_output_domain("alsa_output"); + +static const char * +alsa_device(const AlsaOutput *ad) +{ + return ad->device.empty() ? default_device : ad->device.c_str(); +} + +static void +alsa_configure(AlsaOutput *ad, const config_param ¶m) +{ + ad->device = param.GetBlockValue("device", ""); + + ad->use_mmap = param.GetBlockValue("use_mmap", false); + + ad->dsd_usb = param.GetBlockValue("dsd_usb", false); + + ad->buffer_time = param.GetBlockValue("buffer_time", + MPD_ALSA_BUFFER_TIME_US); + ad->period_time = param.GetBlockValue("period_time", 0u); + +#ifdef SND_PCM_NO_AUTO_RESAMPLE + if (!param.GetBlockValue("auto_resample", true)) + ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; +#endif + +#ifdef SND_PCM_NO_AUTO_CHANNELS + if (!param.GetBlockValue("auto_channels", true)) + ad->mode |= SND_PCM_NO_AUTO_CHANNELS; +#endif + +#ifdef SND_PCM_NO_AUTO_FORMAT + if (!param.GetBlockValue("auto_format", true)) + ad->mode |= SND_PCM_NO_AUTO_FORMAT; +#endif +} + +static struct audio_output * +alsa_init(const config_param ¶m, Error &error) +{ + AlsaOutput *ad = new AlsaOutput(); + + if (!ad->Init(param, error)) { + delete ad; + return NULL; + } + + alsa_configure(ad, param); + + return &ad->base; +} + +static void +alsa_finish(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->Deinit(); + delete ad; + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +static bool +alsa_output_enable(struct audio_output *ao, gcc_unused Error &error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->pcm_export.Construct(); + return true; +} + +static void +alsa_output_disable(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->pcm_export.Destruct(); +} + +static bool +alsa_test_default_device(void) +{ + snd_pcm_t *handle; + + int ret = snd_pcm_open(&handle, default_device, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (ret) { + FormatError(alsa_output_domain, + "Error opening default ALSA device: %s", + snd_strerror(-ret)); + return false; + } else + snd_pcm_close(handle); + + return true; +} + +static snd_pcm_format_t +get_bitformat(SampleFormat sample_format) +{ + switch (sample_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + return SND_PCM_FORMAT_UNKNOWN; + + case SampleFormat::S8: + return SND_PCM_FORMAT_S8; + + case SampleFormat::S16: + return SND_PCM_FORMAT_S16; + + case SampleFormat::S24_P32: + return SND_PCM_FORMAT_S24; + + case SampleFormat::S32: + return SND_PCM_FORMAT_S32; + + case SampleFormat::FLOAT: + return SND_PCM_FORMAT_FLOAT; + } + + assert(false); + gcc_unreachable(); +} + +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; + } +} + +static snd_pcm_format_t +alsa_to_packed_format(snd_pcm_format_t fmt) +{ + switch (fmt) { + case SND_PCM_FORMAT_S24_LE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_BE: + return SND_PCM_FORMAT_S24_3BE; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static int +alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t fmt, bool *packed_r) +{ + int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = false; + + if (err != -EINVAL) + return err; + + fmt = alsa_to_packed_format(fmt); + if (fmt == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = 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(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + SampleFormat sample_format, + bool *packed_r, bool *reverse_endian_r) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, + packed_r); + if (err == 0) + *reverse_endian_r = false; + + if (err != -EINVAL) + return err; + + alsa_format = byteswap_bitformat(alsa_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); + if (err == 0) + *reverse_endian_r = true; + + return err; +} + +/** + * 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, + AudioFormat &audio_format, + bool *packed_r, bool *reverse_endian_r) +{ + /* try the input format first */ + + int err = alsa_output_try_format(pcm, hwparams, + audio_format.format, + packed_r, reverse_endian_r); + + /* if unsupported by the hardware, try other formats */ + + static const SampleFormat probe_formats[] = { + SampleFormat::S24_P32, + SampleFormat::S32, + SampleFormat::S16, + SampleFormat::S8, + SampleFormat::UNDEFINED, + }; + + for (unsigned i = 0; + err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED; + ++i) { + const SampleFormat mpd_format = probe_formats[i]; + if (mpd_format == audio_format.format) + continue; + + err = alsa_output_try_format(pcm, hwparams, mpd_format, + packed_r, reverse_endian_r); + if (err == 0) + audio_format.format = mpd_format; + } + + return err; +} + +/** + * Set up the snd_pcm_t object which was opened by the caller. Set up + * the configured settings and the audio format. + */ +static bool +alsa_setup(AlsaOutput *ad, AudioFormat &audio_format, + bool *packed_r, bool *reverse_endian_r, Error &error) +{ + unsigned int sample_rate = audio_format.sample_rate; + unsigned int channels = audio_format.channels; + int err; + const char *cmd = NULL; + int retry = MPD_ALSA_RETRY_NR; + unsigned int period_time, period_time_ro; + unsigned int buffer_time; + + period_time_ro = period_time = ad->period_time; +configure_hw: + /* configure HW params */ + snd_pcm_hw_params_t *hwparams; + snd_pcm_hw_params_alloca(&hwparams); + cmd = "snd_pcm_hw_params_any"; + err = snd_pcm_hw_params_any(ad->pcm, hwparams); + if (err < 0) + goto error; + + if (ad->use_mmap) { + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (err < 0) { + FormatWarning(alsa_output_domain, + "Cannot set mmap'ed mode on ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(-err)); + LogWarning(alsa_output_domain, + "Falling back to direct write mode"); + ad->use_mmap = false; + } else + ad->writei = snd_pcm_mmap_writei; + } + + if (!ad->use_mmap) { + cmd = "snd_pcm_hw_params_set_access"; + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + goto error; + ad->writei = snd_pcm_writei; + } + + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, + packed_r, reverse_endian_r); + if (err < 0) { + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(audio_format.format), + snd_strerror(-err)); + return false; + } + + snd_pcm_format_t format; + if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) + FormatDebug(alsa_output_domain, + "format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); + + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, + &channels); + if (err < 0) { + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support %i channels: %s", + alsa_device(ad), (int)audio_format.channels, + snd_strerror(-err)); + return false; + } + audio_format.channels = (int8_t)channels; + + err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, + &sample_rate, NULL); + if (err < 0 || sample_rate == 0) { + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support %u Hz audio", + alsa_device(ad), audio_format.sample_rate); + return false; + } + 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); + FormatDebug(alsa_output_domain, "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); + FormatDebug(alsa_output_domain, "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"; + err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, + &buffer_time, NULL); + if (err < 0) + goto error; + } else { + err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, + NULL); + if (err < 0) + buffer_time = 0; + } + + if (period_time_ro == 0 && buffer_time >= 10000) { + period_time_ro = period_time = buffer_time / 4; + + FormatDebug(alsa_output_domain, + "default period_time = buffer_time/4 = %u/4 = %u", + buffer_time, period_time); + } + + if (period_time_ro > 0) { + period_time = period_time_ro; + cmd = "snd_pcm_hw_params_set_period_time_near"; + err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, + &period_time, NULL); + if (err < 0) + goto error; + } + + cmd = "snd_pcm_hw_params"; + err = snd_pcm_hw_params(ad->pcm, hwparams); + if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { + period_time_ro = period_time_ro >> 1; + goto configure_hw; + } else if (err < 0) + goto error; + if (retry != MPD_ALSA_RETRY_NR) + FormatDebug(alsa_output_domain, + "ALSA period_time set to %d", period_time); + + snd_pcm_uframes_t alsa_buffer_size; + cmd = "snd_pcm_hw_params_get_buffer_size"; + err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); + if (err < 0) + goto error; + + snd_pcm_uframes_t alsa_period_size; + cmd = "snd_pcm_hw_params_get_period_size"; + err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, + NULL); + if (err < 0) + goto error; + + /* configure SW params */ + snd_pcm_sw_params_t *swparams; + snd_pcm_sw_params_alloca(&swparams); + + cmd = "snd_pcm_sw_params_current"; + err = snd_pcm_sw_params_current(ad->pcm, swparams); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_start_threshold"; + err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, + alsa_buffer_size - + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_avail_min"; + err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params"; + err = snd_pcm_sw_params(ad->pcm, swparams); + if (err < 0) + goto error; + + FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u", + (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + + if (alsa_period_size == 0) + /* this works around a SIGFPE bug that occurred when + an ALSA driver indicated period_size==0; this + caused a division by zero in alsa_play(). By using + the fallback "1", we make sure that this won't + happen again. */ + alsa_period_size = 1; + + ad->period_frames = alsa_period_size; + ad->period_position = 0; + + ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, + alsa_period_size)); + snd_pcm_format_set_silence(format, ad->silence, + alsa_period_size * channels); + + return true; + +error: + error.Format(alsa_output_domain, err, + "Error opening ALSA device \"%s\" (%s): %s", + alsa_device(ad), cmd, snd_strerror(-err)); + return false; +} + +static bool +alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + Error &error) +{ + assert(ad->dsd_usb); + assert(audio_format.format == SampleFormat::DSD); + + /* pass 24 bit to alsa_setup() */ + + AudioFormat usb_format = audio_format; + usb_format.format = SampleFormat::S24_P32; + usb_format.sample_rate /= 2; + + const AudioFormat check = usb_format; + + if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error)) + return false; + + /* if the device allows only 32 bit, shift all DSD-over-USB + samples left by 8 bit and leave the lower 8 bit cleared; + the DSD-over-USB documentation does not specify whether + this is legal, but there is anecdotical evidence that this + is possible (and the only option for some devices) */ + *shift8_r = usb_format.format == SampleFormat::S32; + if (usb_format.format == SampleFormat::S32) + usb_format.format = SampleFormat::S24_P32; + + if (usb_format != check) { + /* no bit-perfect playback, which is required + for DSD over USB */ + error.Format(alsa_output_domain, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + g_free(ad->silence); + return false; + } + + return true; +} + +static bool +alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format, + Error &error) +{ + bool shift8 = false, packed, reverse_endian; + + const bool dsd_usb = ad->dsd_usb && + audio_format.format == SampleFormat::DSD; + const bool success = dsd_usb + ? alsa_setup_dsd(ad, audio_format, + &shift8, &packed, &reverse_endian, + error) + : alsa_setup(ad, audio_format, &packed, &reverse_endian, + error); + if (!success) + return false; + + ad->pcm_export->Open(audio_format.format, + audio_format.channels, + dsd_usb, shift8, packed, reverse_endian); + return true; +} + +static bool +alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + int err = snd_pcm_open(&ad->pcm, alsa_device(ad), + SND_PCM_STREAM_PLAYBACK, ad->mode); + if (err < 0) { + error.Format(alsa_output_domain, err, + "Failed to open ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(err)); + return false; + } + + FormatDebug(alsa_output_domain, "opened %s type=%s", + snd_pcm_name(ad->pcm), + snd_pcm_type_name(snd_pcm_type(ad->pcm))); + + if (!alsa_setup_or_dsd(ad, audio_format, error)) { + snd_pcm_close(ad->pcm); + return false; + } + + ad->in_frame_size = audio_format.GetFrameSize(); + ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format); + + return true; +} + +/** + * Write silence to the ALSA device. + */ +static void +alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes) +{ + ad->writei(ad->pcm, ad->silence, nframes); +} + +static int +alsa_recover(AlsaOutput *ad, int err) +{ + if (err == -EPIPE) { + FormatDebug(alsa_output_domain, + "Underrun on ALSA device \"%s\"", alsa_device(ad)); + } else if (err == -ESTRPIPE) { + FormatDebug(alsa_output_domain, + "ALSA device \"%s\" was suspended", + alsa_device(ad)); + } + + switch (snd_pcm_state(ad->pcm)) { + case SND_PCM_STATE_PAUSED: + err = snd_pcm_pause(ad->pcm, /* disable */ 0); + break; + case SND_PCM_STATE_SUSPENDED: + err = snd_pcm_resume(ad->pcm); + if (err == -EAGAIN) + return 0; + /* 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); + + if (err == 0) { + /* this works around a driver bug observed on + the Raspberry Pi: after snd_pcm_drop(), the + whole ring buffer must be invalidated, but + the snd_pcm_prepare() call above makes the + driver play random data that just happens + to be still in the buffer; by adding and + cancelling some silence, this bug does not + occur */ + alsa_write_silence(ad, ad->period_frames); + + /* cancel the silence data right away to avoid + increasing latency; even though this + function call invalidates the portion of + silence, the driver seems to avoid the + bug */ + snd_pcm_reset(ad->pcm); + } + + break; + case SND_PCM_STATE_DISCONNECTED: + break; + /* this is no error, so just keep running */ + case SND_PCM_STATE_RUNNING: + err = 0; + break; + default: + /* unknown state, do nothing */ + break; + } + + return err; +} + +static void +alsa_drain(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + 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; + alsa_write_silence(ad, nframes); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void +alsa_cancel(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); +} + +static void +alsa_close(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + snd_pcm_close(ad->pcm); + g_free(ad->silence); +} + +static size_t +alsa_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + assert(size % ad->in_frame_size == 0); + + chunk = ad->pcm_export->Export(chunk, size, size); + + assert(size % ad->out_frame_size == 0); + + size /= ad->out_frame_size; + + while (true) { + snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; + + size_t bytes_written = ret * ad->out_frame_size; + return ad->pcm_export->CalcSourceSize(bytes_written); + } + + if (ret < 0 && ret != -EAGAIN && ret != -EINTR && + alsa_recover(ad, ret) < 0) { + error.Set(alsa_output_domain, ret, snd_strerror(-ret)); + return 0; + } + } +} + +const struct audio_output_plugin alsa_output_plugin = { + "alsa", + alsa_test_default_device, + alsa_init, + alsa_finish, + alsa_output_enable, + alsa_output_disable, + alsa_open, + alsa_close, + nullptr, + nullptr, + alsa_play, + alsa_drain, + alsa_cancel, + nullptr, + + &alsa_mixer_plugin, +}; diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx new file mode 100644 index 000000000..dc7e639a8 --- /dev/null +++ b/src/output/AlsaOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX +#define MPD_ALSA_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin alsa_output_plugin; + +#endif diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx new file mode 100644 index 000000000..e66969e20 --- /dev/null +++ b/src/output/AoOutputPlugin.cxx @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AoOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <ao/ao.h> +#include <glib.h> + +#include <string.h> + +/* An ao_sample_format, with all fields set to zero: */ +static ao_sample_format OUR_AO_FORMAT_INITIALIZER; + +static unsigned ao_output_ref; + +struct AoOutput { + struct audio_output base; + + size_t write_size; + int driver; + ao_option *options; + ao_device *device; + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &ao_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain ao_output_domain("ao_output"); + +static void +ao_output_error(Error &error_r) +{ + const char *error; + + switch (errno) { + case AO_ENODRIVER: + error = "No such libao driver"; + break; + + case AO_ENOTLIVE: + error = "This driver is not a libao live device"; + break; + + case AO_EBADOPTION: + error = "Invalid libao option"; + break; + + case AO_EOPENDEVICE: + error = "Cannot open the libao device"; + break; + + case AO_EFAIL: + error = "Generic libao failure"; + break; + + default: + error_r.SetErrno(); + return; + } + + error_r.Set(ao_output_domain, errno, error); +} + +inline bool +AoOutput::Configure(const config_param ¶m, Error &error) +{ + const char *value; + + options = nullptr; + + write_size = param.GetBlockValue("write_size", 1024u); + + if (ao_output_ref == 0) { + ao_initialize(); + } + ao_output_ref++; + + value = param.GetBlockValue("driver", "default"); + if (0 == strcmp(value, "default")) + driver = ao_default_driver_id(); + else + driver = ao_driver_id(value); + + if (driver < 0) { + error.Format(ao_output_domain, + "\"%s\" is not a valid ao driver", + value); + return false; + } + + ao_info *ai = ao_driver_info(driver); + if (ai == nullptr) { + error.Set(ao_output_domain, "problems getting driver info"); + return false; + } + + FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n", + ai->short_name, param.GetBlockValue("name", nullptr)); + + value = param.GetBlockValue("options", nullptr); + if (value != nullptr) { + gchar **_options = g_strsplit(value, ";", 0); + + for (unsigned i = 0; _options[i] != nullptr; ++i) { + gchar **key_value = g_strsplit(_options[i], "=", 2); + + if (key_value[0] == nullptr || key_value[1] == nullptr) { + error.Format(ao_output_domain, + "problems parsing options \"%s\"", + _options[i]); + return false; + } + + ao_append_option(&options, key_value[0], + key_value[1]); + + g_strfreev(key_value); + } + + g_strfreev(_options); + } + + return true; +} + +static struct audio_output * +ao_output_init(const config_param ¶m, Error &error) +{ + AoOutput *ad = new AoOutput(); + + if (!ad->Initialize(param, error)) { + delete ad; + return nullptr; + } + + if (!ad->Configure(param, error)) { + ad->Deinitialize(); + delete ad; + return nullptr; + } + + return &ad->base; +} + +static void +ao_output_finish(struct audio_output *ao) +{ + AoOutput *ad = (AoOutput *)ao; + + ao_free_options(ad->options); + ad->Deinitialize(); + delete ad; + + ao_output_ref--; + + if (ao_output_ref == 0) + ao_shutdown(); +} + +static void +ao_output_close(struct audio_output *ao) +{ + AoOutput *ad = (AoOutput *)ao; + + ao_close(ad->device); +} + +static bool +ao_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; + AoOutput *ad = (AoOutput *)ao; + + switch (audio_format.format) { + case SampleFormat::S8: + format.bits = 8; + break; + + case SampleFormat::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 = SampleFormat::S16; + format.bits = 16; + break; + } + + format.rate = audio_format.sample_rate; + format.byte_format = AO_FMT_NATIVE; + format.channels = audio_format.channels; + + ad->device = ao_open_live(ad->driver, &format, ad->options); + + if (ad->device == nullptr) { + ao_output_error(error); + return false; + } + + return true; +} + +/** + * For whatever reason, libao wants a non-const pointer. Let's hope + * it does not write to the buffer, and use the union deconst hack to + * work around this API misdesign. + */ +static int ao_play_deconst(ao_device *device, const void *output_samples, + uint_32 num_bytes) +{ + union { + const void *in; + char *out; + } u; + + u.in = output_samples; + return ao_play(device, u.out, num_bytes); +} + +static size_t +ao_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + AoOutput *ad = (AoOutput *)ao; + + if (size > ad->write_size) + size = ad->write_size; + + if (ao_play_deconst(ad->device, chunk, size) == 0) { + ao_output_error(error); + return 0; + } + + return size; +} + +const struct audio_output_plugin ao_output_plugin = { + "ao", + nullptr, + ao_output_init, + ao_output_finish, + nullptr, + nullptr, + ao_output_open, + ao_output_close, + nullptr, + nullptr, + ao_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/AoOutputPlugin.hxx b/src/output/AoOutputPlugin.hxx new file mode 100644 index 000000000..a44885e56 --- /dev/null +++ b/src/output/AoOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AO_OUTPUT_PLUGIN_HXX +#define MPD_AO_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin ao_output_plugin; + +#endif diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx new file mode 100644 index 000000000..babda5f9e --- /dev/null +++ b/src/output/FifoOutputPlugin.cxx @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FifoOutputPlugin.hxx" +#include "ConfigError.hxx" +#include "OutputAPI.hxx" +#include "Timer.hxx" +#include "system/fd_util.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "open.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ + +struct FifoOutput { + struct audio_output base; + + Path path; + std::string path_utf8; + + int input; + int output; + bool created; + Timer *timer; + + FifoOutput() + :path(Path::Null()), input(-1), output(-1), created(false) {} + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &fifo_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Create(Error &error); + bool Check(Error &error); + void Delete(); + + bool Open(Error &error); + void Close(); +}; + +static constexpr Domain fifo_output_domain("fifo_output"); + +inline void +FifoOutput::Delete() +{ + FormatDebug(fifo_output_domain, + "Removing FIFO \"%s\"", path_utf8.c_str()); + + if (!RemoveFile(path)) { + FormatErrno(fifo_output_domain, + "Could not remove FIFO \"%s\"", + path_utf8.c_str()); + return; + } + + created = false; +} + +void +FifoOutput::Close() +{ + if (input >= 0) { + close(input); + input = -1; + } + + if (output >= 0) { + close(output); + output = -1; + } + + struct stat st; + if (created && StatFile(path, st)) + Delete(); +} + +inline bool +FifoOutput::Create(Error &error) +{ + if (!MakeFifo(path, 0666)) { + error.FormatErrno("Couldn't create FIFO \"%s\"", + path_utf8.c_str()); + return false; + } + + created = true; + return true; +} + +inline bool +FifoOutput::Check(Error &error) +{ + struct stat st; + if (!StatFile(path, st)) { + if (errno == ENOENT) { + /* Path doesn't exist */ + return Create(error); + } + + error.FormatErrno("Failed to stat FIFO \"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISFIFO(st.st_mode)) { + error.Format(fifo_output_domain, + "\"%s\" already exists, but is not a FIFO", + path_utf8.c_str()); + return false; + } + + return true; +} + +inline bool +FifoOutput::Open(Error &error) +{ + if (!Check(error)) + return false; + + input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0); + if (input < 0) { + error.FormatErrno("Could not open FIFO \"%s\" for reading", + path_utf8.c_str()); + Close(); + return false; + } + + output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0); + if (output < 0) { + error.FormatErrno("Could not open FIFO \"%s\" for writing", + path_utf8.c_str()); + Close(); + return false; + } + + return true; +} + +static bool +fifo_open(FifoOutput *fd, Error &error) +{ + return fd->Open(error); +} + +static struct audio_output * +fifo_output_init(const config_param ¶m, Error &error) +{ + FifoOutput *fd = new FifoOutput(); + + fd->path = param.GetBlockPath("path", error); + if (fd->path.IsNull()) { + delete fd; + + if (!error.IsDefined()) + error.Set(config_domain, + "No \"path\" parameter specified"); + return nullptr; + } + + fd->path_utf8 = fd->path.ToUTF8(); + + if (!fd->Initialize(param, error)) { + delete fd; + return nullptr; + } + + if (!fifo_open(fd, error)) { + fd->Deinitialize(); + delete fd; + return nullptr; + } + + return &fd->base; +} + +static void +fifo_output_finish(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + fd->Close(); + fd->Deinitialize(); + delete fd; +} + +static bool +fifo_output_open(struct audio_output *ao, AudioFormat &audio_format, + gcc_unused Error &error) +{ + FifoOutput *fd = (FifoOutput *)ao; + + fd->timer = new Timer(audio_format); + + return true; +} + +static void +fifo_output_close(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + delete fd->timer; +} + +static void +fifo_output_cancel(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + char buf[FIFO_BUFFER_SIZE]; + int bytes = 1; + + fd->timer->Reset(); + + while (bytes > 0 && errno != EINTR) + bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); + + if (bytes < 0 && errno != EAGAIN) { + FormatErrno(fifo_output_domain, + "Flush of FIFO \"%s\" failed", + fd->path_utf8.c_str()); + } +} + +static unsigned +fifo_output_delay(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + return fd->timer->IsStarted() + ? fd->timer->GetDelay() + : 0; +} + +static size_t +fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + FifoOutput *fd = (FifoOutput *)ao; + ssize_t bytes; + + if (!fd->timer->IsStarted()) + fd->timer->Start(); + fd->timer->Add(size); + + while (true) { + bytes = write(fd->output, chunk, size); + if (bytes > 0) + return (size_t)bytes; + + if (bytes < 0) { + switch (errno) { + case EAGAIN: + /* The pipe is full, so empty it */ + fifo_output_cancel(&fd->base); + continue; + case EINTR: + continue; + } + + error.FormatErrno("Failed to write to FIFO %s", + fd->path_utf8.c_str()); + return 0; + } + } +} + +const struct audio_output_plugin fifo_output_plugin = { + "fifo", + nullptr, + fifo_output_init, + fifo_output_finish, + nullptr, + nullptr, + fifo_output_open, + fifo_output_close, + fifo_output_delay, + nullptr, + fifo_output_play, + nullptr, + fifo_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/FifoOutputPlugin.hxx new file mode 100644 index 000000000..dca2886d8 --- /dev/null +++ b/src/output/FifoOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX +#define MPD_FIFO_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin fifo_output_plugin; + +#endif diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..f7ae8d10c --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "system/SocketError.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (current_page != nullptr) + current_page->Unref(); + + for (auto page : pages) + page->Unref(); + } + + if (metadata) + metadata->Unref(); +} + +void +HttpdClient::Close() +{ + httpd->RemoveClient(*this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + current_page = nullptr; + + httpd->SendHeader(*this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + LogWarning(httpd_output_domain, + "malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); + if (gcc_unlikely(nbytes < 0)) { + const SocketErrorMessage msg; + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + Close(); + return false; + } + + return true; +} + +HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, + bool _metadata_supported) + :BufferedSocket(_fd, _loop), + httpd(_httpd), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page->Unref(); + pages.clear(); + + if (current_page == nullptr) + CancelWrite(); +} + +ssize_t +HttpdClient::TryWritePage(const Page &page, size_t position) +{ + assert(position < page.size); + + return Write(page.data + position, page.size - position); +} + +ssize_t +HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n) +{ + return n >= 0 + ? Write(page.data + position, n) + : TryWritePage(page, position); +} + +ssize_t +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::TryWrite() +{ + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (current_page == nullptr) { + if (pages.empty()) { + /* another thread has removed the event source + while this thread was waiting for + httpd->mutex */ + CancelWrite(); + return true; + } + + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const ssize_t bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + if (!metadata_sent) { + ssize_t nbytes = TryWritePage(*metadata, + metadata_current_position); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_current_position += nbytes; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + guchar empty_data = 0; + + ssize_t nbytes = Write(&empty_data, 1); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_fill = 0; + metadata_current_position = 0; + } + } else { + ssize_t nbytes = + TryWritePageN(*current_page, current_position, + bytes_to_write); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + current_position += nbytes; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += nbytes; + + if (current_position >= current_page->size) { + current_page->Unref(); + current_page = nullptr; + + if (pages.empty()) + /* all pages are sent: remove the + event source */ + CancelWrite(); + } + } + + return true; +} + +void +HttpdClient::PushPage(Page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page->Ref(); + pages.push_back(page); + + ScheduleWrite(); +} + +void +HttpdClient::PushMetaData(Page *page) +{ + if (metadata) { + metadata->Unref(); + metadata = nullptr; + } + + g_return_if_fail (page); + + page->Ref(); + metadata = page; + metadata_sent = false; +} + +bool +HttpdClient::OnSocketReady(unsigned flags) +{ + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (flags & WRITE) + if (!TryWrite()) + return false; + + return true; +} + +BufferedSocket::InputResult +HttpdClient::OnSocketInput(const void *data, size_t length) +{ + if (state == RESPONSE) { + LogWarning(httpd_output_domain, + "unexpected input from client"); + LockClose(); + return InputResult::CLOSED; + } + + const char *line = (const char *)data; + const char *newline = (const char *)memchr(line, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + ConsumeInput(newline + 1 - line); + + if (newline > line && newline[-1] == '\r') + --newline; + + /* terminate the string at the end of the line; the const_cast + is a dirty hack */ + *const_cast<char *>(newline) = 0; + + if (!HandleLine(line)) { + assert(state == RESPONSE); + LockClose(); + return InputResult::CLOSED; + } + + if (state == RESPONSE && !SendResponse()) + return InputResult::CLOSED; + + return InputResult::AGAIN; +} + +void +HttpdClient::OnSocketError(Error &&error) +{ + LogError(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..a596814ee --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OUTPUT_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "event/BufferedSocket.hxx" +#include "gcc.h" + +#include <list> + +#include <stddef.h> + +struct HttpdOutput; +class Page; + +class HttpdClient final : public BufferedSocket { + /** + * The httpd output object this client is connected to. + */ + HttpdOutput *const httpd; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #Page objects to be sent to the client. + */ + std::list<Page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + Page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + unsigned metaint; + + /** + * The metadata as #Page which is currently being sent to the client. + */ + Page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + unsigned metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, + bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #HttpdOutput object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + ssize_t GetBytesTillMetaData() const; + + ssize_t TryWritePage(const Page &page, size_t position); + ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n); + + bool TryWrite(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(Page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(Page *page); + +protected: + virtual bool OnSocketReady(unsigned flags) override; + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(Error &&error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx new file mode 100644 index 000000000..445e2ff1a --- /dev/null +++ b/src/output/HttpdInternal.hxx @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Internal declarations for the "httpd" audio output plugin. + */ + +#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H +#define MPD_OUTPUT_HTTPD_INTERNAL_H + +#include "OutputInternal.hxx" +#include "Timer.hxx" +#include "thread/Mutex.hxx" +#include "event/ServerSocket.hxx" + +#include <forward_list> + +struct config_param; +class Error; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; +struct Encoder; +struct Tag; + +struct HttpdOutput final : private ServerSocket { + struct audio_output base; + + /** + * True if the audio output is open and accepts client + * connections. + */ + bool open; + + /** + * The configured encoder plugin. + */ + Encoder *encoder; + + /** + * Number of bytes which were fed into the encoder, without + * ever receiving new output. This is used to estimate + * whether MPD should manually flush the encoder, to avoid + * buffer underruns in the client. + */ + size_t unflushed_input; + + /** + * The MIME type produced by the #encoder. + */ + const char *content_type; + + /** + * This mutex protects the listener socket and the client + * list. + */ + mutable Mutex mutex; + + /** + * A #Timer object to synchronize this output with the + * wallclock. + */ + Timer *timer; + + /** + * The header page, which is sent to every client on connect. + */ + Page *header; + + /** + * The metadata, which is sent to every client. + */ + 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. + */ + std::forward_list<HttpdClient> clients; + + /** + * A temporary buffer for the httpd_output_read_page() + * function. + */ + char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + unsigned clients_max, clients_cnt; + + HttpdOutput(EventLoop &_loop); + ~HttpdOutput(); + + bool Configure(const config_param ¶m, Error &error); + + bool Bind(Error &error); + void Unbind(); + + /** + * Caller must lock the mutex. + */ + bool OpenEncoder(AudioFormat &audio_format, Error &error); + + /** + * Caller must lock the mutex. + */ + bool Open(AudioFormat &audio_format, Error &error); + + /** + * Caller must lock the mutex. + */ + void Close(); + + /** + * Check whether there is at least one client. + * + * Caller must lock the mutex. + */ + gcc_pure + bool HasClients() const { + return !clients.empty(); + } + + /** + * Check whether there is at least one client. + */ + gcc_pure + bool LockHasClients() const { + const ScopeLock protect(mutex); + return HasClients(); + } + + void AddClient(int fd); + + /** + * Removes a client from the httpd_output.clients linked list. + */ + void RemoveClient(HttpdClient &client); + + /** + * Sends the encoder header to the client. This is called + * right after the response headers have been sent. + */ + void SendHeader(HttpdClient &client) const; + + /** + * Reads data from the encoder (as much as available) and + * returns it as a new #page object. + */ + Page *ReadPage(); + + /** + * Broadcasts a page struct to all clients. + * + * Mutext must not be locked. + */ + void BroadcastPage(Page *page); + + /** + * Broadcasts data from the encoder to all clients. + */ + void BroadcastFromEncoder(); + + bool EncodeAndPlay(const void *chunk, size_t size, Error &error); + + void SendTag(const Tag *tag); + +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) override; +}; + +extern const class Domain httpd_output_domain; + +#endif diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx new file mode 100644 index 000000000..09f0f5e6b --- /dev/null +++ b/src/output/HttpdOutputPlugin.cxx @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" +#include "OutputAPI.hxx" +#include "EncoderPlugin.hxx" +#include "EncoderList.hxx" +#include "system/Resolver.hxx" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "system/fd_util.h" +#include "Main.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> + +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#ifdef HAVE_LIBWRAP +#include <sys/socket.h> /* needed for AF_UNIX */ +#include <tcpd.h> +#endif + +const Domain httpd_output_domain("httpd_output"); + +inline +HttpdOutput::HttpdOutput(EventLoop &_loop) + :ServerSocket(_loop), + encoder(nullptr), unflushed_input(0), + metadata(nullptr) +{ +} + +HttpdOutput::~HttpdOutput() +{ + if (metadata != nullptr) + metadata->Unref(); + + if (encoder != nullptr) + encoder_finish(encoder); + +} + +inline bool +HttpdOutput::Bind(Error &error) +{ + open = false; + + const ScopeLock protect(mutex); + return ServerSocket::Open(error); +} + +inline void +HttpdOutput::Unbind() +{ + assert(!open); + + const ScopeLock protect(mutex); + ServerSocket::Close(); +} + +inline bool +HttpdOutput::Configure(const config_param ¶m, Error &error) +{ + /* read configuration */ + name = param.GetBlockValue("name", "Set name in config"); + genre = param.GetBlockValue("genre", "Set genre in config"); + website = param.GetBlockValue("website", "Set website in config"); + + unsigned port = param.GetBlockValue("port", 8000u); + + const char *encoder_name = + param.GetBlockValue("encoder", "vorbis"); + const auto encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + error.Format(httpd_output_domain, + "No such encoder: %s", encoder_name); + return false; + } + + clients_max = param.GetBlockValue("max_clients", 0u); + + /* set up bind_to_address */ + + const char *bind_to_address = param.GetBlockValue("bind_to_address"); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? AddHost(bind_to_address, port, error) + : AddPort(port, error); + if (!success) + return false; + + /* initialize encoder */ + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + /* determine content type */ + content_type = encoder_get_mime_type(encoder); + if (content_type == nullptr) + content_type = "application/octet-stream"; + + return true; +} + +static struct audio_output * +httpd_output_init(const config_param ¶m, Error &error) +{ + HttpdOutput *httpd = new HttpdOutput(*main_loop); + + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, + error)) { + delete httpd; + return nullptr; + } + + if (!httpd->Configure(param, error)) { + ao_base_finish(&httpd->base); + delete httpd; + return nullptr; + } + + return &httpd->base; +} + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +static inline constexpr HttpdOutput * +Cast(audio_output *ao) +{ + return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base)); +} + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +static void +httpd_output_finish(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + ao_base_finish(&httpd->base); + delete httpd; +} + +/** + * Creates a new #HttpdClient object and adds it into the + * HttpdOutput.clients linked list. + */ +inline void +HttpdOutput::AddClient(int fd) +{ + clients.emplace_front(this, fd, GetEventLoop(), + encoder->plugin.tag == nullptr); + ++clients_cnt; + + /* pass metadata to client */ + if (metadata != nullptr) + clients.front().PushMetaData(metadata); +} + +void +HttpdOutput::OnAccept(int fd, const sockaddr &address, + size_t address_length, gcc_unused int uid) +{ + /* the listener socket has become readable - a client has + connected */ + +#ifdef HAVE_LIBWRAP + if (address.sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(&address, address_length, + IgnoreError()); + 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 */ + FormatWarning(httpd_output_domain, + "libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + g_free(hostaddr); + close_socket(fd); + return; + } + + g_free(hostaddr); + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + const ScopeLock protect(mutex); + + if (fd >= 0) { + /* can we allow additional client */ + if (open && (clients_max == 0 || clients_cnt < clients_max)) + AddClient(fd); + else + close_socket(fd); + } else if (fd < 0 && errno != EINTR) { + LogErrno(httpd_output_domain, "accept() failed"); + } +} + +Page * +HttpdOutput::ReadPage() +{ + if (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(encoder, IgnoreError()); + unflushed_input = 0; + } + + size_t size = 0; + do { + size_t nbytes = encoder_read(encoder, + buffer + size, + sizeof(buffer) - size); + if (nbytes == 0) + break; + + unflushed_input = 0; + + size += nbytes; + } while (size < sizeof(buffer)); + + if (size == 0) + return NULL; + + return Page::Copy(buffer, size); +} + +static bool +httpd_output_enable(struct audio_output *ao, Error &error) +{ + HttpdOutput *httpd = Cast(ao); + + return httpd->Bind(error); +} + +static void +httpd_output_disable(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + httpd->Unbind(); +} + +inline bool +HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error) +{ + if (!encoder_open(encoder, audio_format, error)) + return false; + + /* we have to remember the encoder header, i.e. the first + bytes of encoder output after opening it, because it has to + be sent to every new client */ + header = ReadPage(); + + unflushed_input = 0; + + return true; +} + +inline bool +HttpdOutput::Open(AudioFormat &audio_format, Error &error) +{ + assert(!open); + assert(clients.empty()); + + /* open the encoder */ + + if (!OpenEncoder(audio_format, error)) + return false; + + /* initialize other attributes */ + + clients_cnt = 0; + timer = new Timer(audio_format); + + open = true; + + return true; +} + +static bool +httpd_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + HttpdOutput *httpd = Cast(ao); + + assert(httpd->clients.empty()); + + const ScopeLock protect(httpd->mutex); + return httpd->Open(audio_format, error); +} + +inline void +HttpdOutput::Close() +{ + assert(open); + + open = false; + + delete timer; + + clients.clear(); + + if (header != NULL) + header->Unref(); + + encoder_close(encoder); +} + +static void +httpd_output_close(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + const ScopeLock protect(httpd->mutex); + httpd->Close(); +} + +void +HttpdOutput::RemoveClient(HttpdClient &client) +{ + assert(clients_cnt > 0); + + for (auto prev = clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != clients.end()); + if (&*i == &client) { + clients.erase_after(prev); + clients_cnt--; + break; + } + } +} + +void +HttpdOutput::SendHeader(HttpdClient &client) const +{ + if (header != NULL) + client.PushPage(header); +} + +static unsigned +httpd_output_delay(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + if (!httpd->LockHasClients() && httpd->base.pause) { + /* if there's no client and this output is paused, + then httpd_output_pause() will not do anything, it + will not fill the buffer and it will not update the + timer; therefore, we reset the timer here */ + httpd->timer->Reset(); + + /* some arbitrary delay that is long enough to avoid + consuming too much CPU, and short enough to notice + new clients quickly enough */ + return 1000; + } + + return httpd->timer->IsStarted() + ? httpd->timer->GetDelay() + : 0; +} + +void +HttpdOutput::BroadcastPage(Page *page) +{ + assert(page != NULL); + + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushPage(page); +} + +void +HttpdOutput::BroadcastFromEncoder() +{ + mutex.lock(); + for (auto &client : clients) { + if (client.GetQueueSize() > 256 * 1024) { + FormatDebug(httpd_output_domain, + "client is too slow, flushing its queue"); + client.CancelQueue(); + } + } + mutex.unlock(); + + Page *page; + while ((page = ReadPage()) != nullptr) { + BroadcastPage(page); + page->Unref(); + } +} + +inline bool +HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error) +{ + if (!encoder_write(encoder, chunk, size, error)) + return false; + + unflushed_input += size; + + BroadcastFromEncoder(); + return true; +} + +static size_t +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + if (!httpd->EncodeAndPlay(chunk, size, error)) + return 0; + } + + if (!httpd->timer->IsStarted()) + httpd->timer->Start(); + httpd->timer->Add(size); + + return size; +} + +static bool +httpd_output_pause(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + static const char silence[1020] = { 0 }; + return httpd_output_play(ao, silence, sizeof(silence), + IgnoreError()) > 0; + } else { + return true; + } +} + +inline void +HttpdOutput::SendTag(const Tag *tag) +{ + assert(tag != NULL); + + if (encoder->plugin.tag != nullptr) { + /* embed encoder tags */ + + /* flush the current stream, and end it */ + + encoder_pre_tag(encoder, IgnoreError()); + BroadcastFromEncoder(); + + /* send the tag to the encoder - which starts a new + stream now */ + + encoder_tag(encoder, tag, IgnoreError()); + + /* the first page generated by the encoder will now be + used as the new "header" page, which is sent to all + new clients */ + + Page *page = ReadPage(); + if (page != NULL) { + if (header != NULL) + header->Unref(); + header = page; + BroadcastPage(page); + } + } else { + /* use Icy-Metadata */ + + if (metadata != NULL) + metadata->Unref(); + + static constexpr tag_type types[] = { + TAG_ALBUM, TAG_ARTIST, TAG_TITLE, + TAG_NUM_OF_ITEM_TYPES + }; + + metadata = icy_server_metadata_page(*tag, &types[0]); + if (metadata != NULL) { + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushMetaData(metadata); + } + } +} + +static void +httpd_output_tag(struct audio_output *ao, const Tag *tag) +{ + HttpdOutput *httpd = Cast(ao); + + httpd->SendTag(tag); +} + +static void +httpd_output_cancel(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.CancelQueue(); +} + +const struct audio_output_plugin httpd_output_plugin = { + "httpd", + nullptr, + httpd_output_init, + httpd_output_finish, + httpd_output_enable, + httpd_output_disable, + httpd_output_open, + httpd_output_close, + httpd_output_delay, + httpd_output_tag, + httpd_output_play, + nullptr, + httpd_output_cancel, + httpd_output_pause, + nullptr, +}; diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx new file mode 100644 index 000000000..c74d2bd4a --- /dev/null +++ b/src/output/HttpdOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin httpd_output_plugin; + +#endif diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx new file mode 100644 index 000000000..75c7daf81 --- /dev/null +++ b/src/output/JackOutputPlugin.cxx @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "JackOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +#include <glib.h> +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +enum { + MAX_PORTS = 16, +}; + +static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); + +struct JackOutput { + struct audio_output base; + + /** + * 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 */ + AudioFormat 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; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &jack_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +static constexpr Domain jack_output_domain("jack_output"); + +/** + * Determine the number of frames guaranteed to be available on all + * channels. + */ +static jack_nframes_t +mpd_jack_available(const JackOutput *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) +{ + JackOutput *jd = (JackOutput *) arg; + + 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) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + 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) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + + 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) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; +} + +static void +mpd_jack_shutdown(void *arg) +{ + JackOutput *jd = (JackOutput *) arg; + jd->shutdown = true; +} + +static void +set_audioformat(JackOutput *jd, AudioFormat &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 != SampleFormat::S16 && + audio_format.format != SampleFormat::S24_P32) + audio_format.format = SampleFormat::S24_P32; +} + +static void +mpd_jack_error(const char *msg) +{ + LogError(jack_output_domain, msg); +} + +#ifdef HAVE_JACK_SET_INFO_FUNCTION +static void +mpd_jack_info(const char *msg) +{ + LogInfo(jack_output_domain, msg); +} +#endif + +/** + * Disconnect the JACK client. + */ +static void +mpd_jack_disconnect(JackOutput *jd) +{ + assert(jd != nullptr); + assert(jd->client != nullptr); + + jack_deactivate(jd->client); + jack_client_close(jd->client); + jd->client = nullptr; +} + +/** + * Connect the JACK client and performs some basic setup + * (e.g. register callbacks). + */ +static bool +mpd_jack_connect(JackOutput *jd, Error &error) +{ + jack_status_t status; + + assert(jd != nullptr); + + jd->shutdown = false; + + jd->client = jack_client_open(jd->name, jd->options, &status, + jd->server_name); + if (jd->client == nullptr) { + error.Format(jack_output_domain, status, + "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] == nullptr) { + error.Format(jack_output_domain, + "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(const char *source, char **dest, Error &error) +{ + char **list = g_strsplit(source, ",", 0); + unsigned n = 0; + + for (n = 0; list[n] != nullptr; ++n) { + if (n >= MAX_PORTS) { + error.Set(config_domain, + "too many port names"); + return 0; + } + + dest[n] = list[n]; + } + + g_free(list); + + if (n == 0) { + error.Format(config_domain, + "at least one port name expected"); + return 0; + } + + return n; +} + +static struct audio_output * +mpd_jack_init(const config_param ¶m, Error &error) +{ + JackOutput *jd = new JackOutput(); + + if (!jd->Initialize(param, error)) { + delete jd; + return nullptr; + } + + const char *value; + + jd->options = JackNullOption; + + jd->name = param.GetBlockValue("client_name", nullptr); + if (jd->name != nullptr) + jd->options = jack_options_t(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 = param.GetBlockValue("server_name", nullptr); + if (jd->server_name != nullptr) + jd->options = jack_options_t(jd->options | JackServerName); + + if (!param.GetBlockValue("autostart", false)) + jd->options = jack_options_t(jd->options | JackNoStartServer); + + /* configure the source ports */ + + value = param.GetBlockValue("source_ports", "left,right"); + jd->num_source_ports = parse_port_list(value, + jd->source_ports, error); + if (jd->num_source_ports == 0) + return nullptr; + + /* configure the destination ports */ + + value = param.GetBlockValue("destination_ports", nullptr); + if (value == nullptr) { + /* compatibility with MPD < 0.16 */ + value = param.GetBlockValue("ports", nullptr); + if (value != nullptr) + FormatWarning(jack_output_domain, + "deprecated option 'ports' in line %d", + param.line); + } + + if (value != nullptr) { + jd->num_destination_ports = + parse_port_list(value, + jd->destination_ports, error); + if (jd->num_destination_ports == 0) + return nullptr; + } else { + jd->num_destination_ports = 0; + } + + if (jd->num_destination_ports > 0 && + jd->num_destination_ports != jd->num_source_ports) + FormatWarning(jack_output_domain, + "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 = param.GetBlockValue("ringbuffer_size", 32768u); + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return &jd->base; +} + +static void +mpd_jack_finish(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + 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]); + + jd->Deinitialize(); + delete jd; +} + +static bool +mpd_jack_enable(struct audio_output *ao, Error &error) +{ + JackOutput *jd = (JackOutput *)ao; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + jd->ringbuffer[i] = nullptr; + + return mpd_jack_connect(jd, error); +} + +static void +mpd_jack_disable(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + if (jd->client != nullptr) + mpd_jack_disconnect(jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] != nullptr) { + jack_ringbuffer_free(jd->ringbuffer[i]); + jd->ringbuffer[i] = nullptr; + } + } +} + +/** + * Stops the playback on the JACK connection. + */ +static void +mpd_jack_stop(JackOutput *jd) +{ + assert(jd != nullptr); + + if (jd->client == nullptr) + 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(JackOutput *jd, Error &error) +{ + const char *destination_ports[MAX_PORTS], **jports; + const char *duplicate_port = nullptr; + unsigned num_destination_ports; + + assert(jd->client != nullptr); + 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] == nullptr) + 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) ) { + error.Set(jack_output_domain, "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, nullptr, nullptr, + JackPortIsPhysical | JackPortIsInput); + if (jports == nullptr) { + error.Set(jack_output_domain, "no ports found"); + mpd_jack_stop(jd); + return false; + } + + assert(*jports != nullptr); + + for (num_destination_ports = 0; + num_destination_ports < MAX_PORTS && + jports[num_destination_ports] != nullptr; + ++num_destination_ports) { + FormatDebug(jack_output_domain, + "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 = nullptr; + } + + 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) { + error.Format(jack_output_domain, + "Not a valid JACK port: %s", + destination_ports[i]); + + if (jports != nullptr) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (duplicate_port != nullptr) { + /* 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) { + error.Format(jack_output_domain, + "Not a valid JACK port: %s", + duplicate_port); + + if (jports != nullptr) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (jports != nullptr) + free(jports); + + return true; +} + +static bool +mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + JackOutput *jd = (JackOutput *)ao; + + assert(jd != nullptr); + + jd->pause = false; + + if (jd->client != nullptr && jd->shutdown) + mpd_jack_disconnect(jd); + + if (jd->client == nullptr && !mpd_jack_connect(jd, error)) + return false; + + set_audioformat(jd, audio_format); + jd->audio_format = audio_format; + + if (!mpd_jack_start(jd, error)) + return false; + + return true; +} + +static void +mpd_jack_close(gcc_unused struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + mpd_jack_stop(jd); +} + +static unsigned +mpd_jack_delay(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + return jd->base.pause && jd->pause && !jd->shutdown + ? 1000 + : 0; +} + +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(JackOutput *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], + (const char *)&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(JackOutput *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], + (const char *)&sample, + sizeof(sample)); + } + } +} + +static void +mpd_jack_write_samples(JackOutput *jd, const void *src, + unsigned num_samples) +{ + switch (jd->audio_format.format) { + case SampleFormat::S16: + mpd_jack_write_samples_16(jd, (const int16_t*)src, + num_samples); + break; + + case SampleFormat::S24_P32: + mpd_jack_write_samples_24(jd, (const int32_t*)src, + num_samples); + break; + + default: + assert(false); + gcc_unreachable(); + } +} + +static size_t +mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + JackOutput *jd = (JackOutput *)ao; + const size_t frame_size = jd->audio_format.GetFrameSize(); + size_t space = 0, space1; + + jd->pause = false; + + assert(size % frame_size == 0); + size /= frame_size; + + while (true) { + if (jd->shutdown) { + error.Set(jack_output_domain, + "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(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + if (jd->shutdown) + return false; + + jd->pause = true; + + return true; +} + +const struct audio_output_plugin jack_output_plugin = { + "jack", + mpd_jack_test_default_device, + mpd_jack_init, + mpd_jack_finish, + mpd_jack_enable, + mpd_jack_disable, + mpd_jack_open, + mpd_jack_close, + mpd_jack_delay, + nullptr, + mpd_jack_play, + nullptr, + nullptr, + mpd_jack_pause, + nullptr, +}; diff --git a/src/output/JackOutputPlugin.hxx b/src/output/JackOutputPlugin.hxx new file mode 100644 index 000000000..908105ad2 --- /dev/null +++ b/src/output/JackOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX +#define MPD_JACK_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin jack_output_plugin; + +#endif diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx new file mode 100644 index 000000000..e2eec9dbc --- /dev/null +++ b/src/output/NullOutputPlugin.cxx @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "NullOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "Timer.hxx" + +#include <assert.h> + +struct NullOutput { + struct audio_output base; + + bool sync; + + Timer *timer; + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &null_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +static struct audio_output * +null_init(const config_param ¶m, Error &error) +{ + NullOutput *nd = new NullOutput(); + + if (!nd->Initialize(param, error)) { + delete nd; + return nullptr; + } + + nd->sync = param.GetBlockValue("sync", true); + + return &nd->base; +} + +static void +null_finish(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + nd->Deinitialize(); + delete nd; +} + +static bool +null_open(struct audio_output *ao, AudioFormat &audio_format, + gcc_unused Error &error) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + nd->timer = new Timer(audio_format); + + return true; +} + +static void +null_close(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + delete nd->timer; +} + +static unsigned +null_delay(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + return nd->sync && nd->timer->IsStarted() + ? nd->timer->GetDelay() + : 0; +} + +static size_t +null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size, + gcc_unused Error &error) +{ + NullOutput *nd = (NullOutput *)ao; + Timer *timer = nd->timer; + + if (!nd->sync) + return size; + + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + + return size; +} + +static void +null_cancel(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (!nd->sync) + return; + + nd->timer->Reset(); +} + +const struct audio_output_plugin null_output_plugin = { + "null", + nullptr, + null_init, + null_finish, + nullptr, + nullptr, + null_open, + null_close, + null_delay, + nullptr, + null_play, + nullptr, + null_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/NullOutputPlugin.hxx b/src/output/NullOutputPlugin.hxx new file mode 100644 index 000000000..a58f1cb13 --- /dev/null +++ b/src/output/NullOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX +#define MPD_NULL_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin null_output_plugin; + +#endif diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx new file mode 100644 index 000000000..eee215b32 --- /dev/null +++ b/src/output/OSXOutputPlugin.cxx @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OSXOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <CoreAudio/AudioHardware.h> +#include <AudioUnit/AudioUnit.h> +#include <CoreServices/CoreServices.h> + +struct OSXOutput { + struct audio_output base; + + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + + AudioUnit au; + Mutex mutex; + Cond condition; + + struct fifo_buffer *buffer; +}; + +static constexpr Domain osx_output_domain("osx_output"); + +static bool +osx_output_test_default_device(void) +{ + /* on a Mac, this is always the default plugin, if nothing + else is configured */ + return true; +} + +static void +osx_output_configure(OSXOutput *oo, const config_param ¶m) +{ + const char *device = param.GetBlockValue("device"); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to g_strdup() this? */ + oo->device_name = device; + } +} + +static struct audio_output * +osx_output_init(const config_param ¶m, Error &error) +{ + OSXOutput *oo = new OSXOutput(); + if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) { + delete oo; + return NULL; + } + + osx_output_configure(oo, param); + + return &oo->base; +} + +static void +osx_output_finish(struct audio_output *ao) +{ + OSXOutput *oo = (OSXOutput *)ao; + + delete oo; +} + +static bool +osx_output_set_device(OSXOutput *oo, Error &error) +{ + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = new AudioDeviceID[numdevices]; + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + FormatDebug(osx_output_domain, + "found matching device: ID=%u, name=%s", + (unsigned)deviceids[i], name); + break; + } + } + if (i == numdevices) { + FormatWarning(osx_output_domain, + "Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } + + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + FormatDebug(osx_output_domain, + "set OS X audio output device ID=%u, name=%s", + (unsigned)deviceids[i], name); + +done: + delete[] deviceids; + return ret; +} + +static OSStatus +osx_render(void *vdata, + gcc_unused AudioUnitRenderActionFlags *io_action_flags, + gcc_unused const AudioTimeStamp *in_timestamp, + gcc_unused UInt32 in_bus_number, + gcc_unused UInt32 in_number_frames, + AudioBufferList *buffer_list) +{ + OSXOutput *od = (OSXOutput *) vdata; + AudioBuffer *buffer = &buffer_list->mBuffers[0]; + size_t buffer_size = buffer->mDataByteSize; + + assert(od->buffer != NULL); + + od->mutex.lock(); + + size_t nbytes; + const void *src = fifo_buffer_read(od->buffer, &nbytes); + + if (src != NULL) { + if (nbytes > buffer_size) + nbytes = buffer_size; + + memcpy(buffer->mData, src, nbytes); + fifo_buffer_consume(od->buffer, nbytes); + } else + nbytes = 0; + + od->condition.signal(); + od->mutex.unlock(); + + buffer->mDataByteSize = nbytes; + + unsigned i; + for (i = 1; i < buffer_list->mNumberBuffers; ++i) { + buffer = &buffer_list->mBuffers[i]; + buffer->mDataByteSize = 0; + } + + return 0; +} + +static bool +osx_output_enable(struct audio_output *ao, Error &error) +{ + OSXOutput *oo = (OSXOutput *)ao; + + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = oo->component_subtype; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == 0) { + error.Set(osx_output_domain, + "Error finding OS X component"); + return false; + } + + OSStatus status = OpenAComponent(comp, &oo->au); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to open OS X component: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + if (!osx_output_set_device(oo, error)) { + CloseComponent(oo->au); + return false; + } + + AURenderCallbackStruct callback; + callback.inputProc = osx_render; + callback.inputProcRefCon = oo; + + ComponentResult result = + AudioUnitSetProperty(oo->au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)); + if (result != noErr) { + CloseComponent(oo->au); + error.Set(osx_output_domain, result, + "unable to set callback for OS X audio unit"); + return false; + } + + return true; +} + +static void +osx_output_disable(struct audio_output *ao) +{ + OSXOutput *oo = (OSXOutput *)ao; + + CloseComponent(oo->au); +} + +static void +osx_output_cancel(struct audio_output *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + const ScopeLock protect(od->mutex); + fifo_buffer_clear(od->buffer); +} + +static void +osx_output_close(struct audio_output *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioOutputUnitStop(od->au); + AudioUnitUninitialize(od->au); + + fifo_buffer_free(od->buffer); +} + +static bool +osx_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioStreamBasicDescription stream_description; + stream_description.mSampleRate = audio_format.sample_rate; + stream_description.mFormatID = kAudioFormatLinearPCM; + stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + + switch (audio_format.format) { + case SampleFormat::S8: + stream_description.mBitsPerChannel = 8; + break; + + case SampleFormat::S16: + stream_description.mBitsPerChannel = 16; + break; + + case SampleFormat::S32: + stream_description.mBitsPerChannel = 32; + break; + + default: + audio_format.format = SampleFormat::S32; + stream_description.mBitsPerChannel = 32; + break; + } + +#if G_BYTE_ORDER == G_BIG_ENDIAN + stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; +#endif + + stream_description.mBytesPerPacket = audio_format.GetFrameSize(); + stream_description.mFramesPerPacket = 1; + stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; + stream_description.mChannelsPerFrame = audio_format.channels; + + ComponentResult result = + AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &stream_description, + sizeof(stream_description)); + if (result != noErr) { + error.Set(osx_output_domain, result, + "Unable to set format on OS X device"); + return false; + } + + OSStatus status = AudioUnitInitialize(od->au); + if (status != noErr) { + error.Set(osx_output_domain, status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + /* create a buffer of 1s */ + od->buffer = fifo_buffer_new(audio_format.sample_rate * + audio_format.GetFrameSize()); + + status = AudioOutputUnitStart(od->au); + if (status != 0) { + AudioUnitUninitialize(od->au); + error.Format(osx_output_domain, status, + "unable to start audio output: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + return true; +} + +static size_t +osx_output_play(struct audio_output *ao, const void *chunk, size_t size, + gcc_unused Error &error) +{ + OSXOutput *od = (OSXOutput *)ao; + + const ScopeLock protect(od->mutex); + + void *dest; + size_t max_length; + + while (true) { + dest = fifo_buffer_write(od->buffer, &max_length); + if (dest != NULL) + break; + + /* wait for some free space in the buffer */ + od->condition.wait(od->mutex); + } + + if (size > max_length) + size = max_length; + + memcpy(dest, chunk, size); + fifo_buffer_append(od->buffer, size); + + return size; +} + +const struct audio_output_plugin osx_output_plugin = { + "osx", + osx_output_test_default_device, + osx_output_init, + osx_output_finish, + osx_output_enable, + osx_output_disable, + osx_output_open, + osx_output_close, + nullptr, + nullptr, + osx_output_play, + nullptr, + osx_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/OSXOutputPlugin.hxx new file mode 100644 index 000000000..2a4172880 --- /dev/null +++ b/src/output/OSXOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX +#define MPD_OSX_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin osx_output_plugin; + +#endif diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx new file mode 100644 index 000000000..e753b206f --- /dev/null +++ b/src/output/OpenALOutputPlugin.cxx @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OpenALOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#ifndef HAVE_OSX +#include <AL/al.h> +#include <AL/alc.h> +#else +#include <OpenAL/al.h> +#include <OpenAL/alc.h> +#endif + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct OpenALOutput { + struct audio_output base; + + const char *device_name; + ALCdevice *device; + ALCcontext *context; + ALuint buffers[NUM_BUFFERS]; + unsigned filled; + ALuint source; + ALenum format; + ALuint frequency; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &openal_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +static constexpr Domain openal_output_domain("openal_output"); + +static ALenum +openal_audio_format(AudioFormat &audio_format) +{ + /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or + AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit + samples, while MPD uses signed samples */ + + switch (audio_format.format) { + case SampleFormat::S16: + if (audio_format.channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format.channels == 1) + return AL_FORMAT_MONO16; + + /* fall back to mono */ + audio_format.channels = 1; + return openal_audio_format(audio_format); + + default: + /* fall back to 16 bit */ + audio_format.format = SampleFormat::S16; + return openal_audio_format(audio_format); + } +} + +gcc_pure +static inline ALint +openal_get_source_i(const OpenALOutput *od, ALenum param) +{ + ALint value; + alGetSourcei(od->source, param, &value); + return value; +} + +gcc_pure +static inline bool +openal_has_processed(const OpenALOutput *od) +{ + return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; +} + +gcc_pure +static inline ALint +openal_is_playing(const OpenALOutput *od) +{ + return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; +} + +static bool +openal_setup_context(OpenALOutput *od, Error &error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == nullptr) { + error.Format(openal_output_domain, + "Error opening OpenAL device \"%s\"", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, nullptr); + + if (od->context == nullptr) { + error.Format(openal_output_domain, + "Error creating context for \"%s\"", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + return true; +} + +static struct audio_output * +openal_init(const config_param ¶m, Error &error) +{ + const char *device_name = param.GetBlockValue("device"); + if (device_name == nullptr) { + device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + OpenALOutput *od = new OpenALOutput(); + if (!od->Initialize(param, error)) { + delete od; + return nullptr; + } + + od->device_name = device_name; + + return &od->base; +} + +static void +openal_finish(struct audio_output *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + od->Deinitialize(); + delete od; +} + +static bool +openal_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + od->format = openal_audio_format(audio_format); + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + error.Set(openal_output_domain, "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + error.Set(openal_output_domain, "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->frequency = audio_format.sample_rate; + + return true; +} + +static void +openal_close(struct audio_output *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static unsigned +openal_delay(struct audio_output *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + return od->filled < NUM_BUFFERS || openal_has_processed(od) + ? 0 + /* we don't know exactly how long we must wait for the + next buffer to finish, so this is a random + guess: */ + : 50; +} + +static size_t +openal_play(struct audio_output *ao, const void *chunk, size_t size, + gcc_unused Error &error) +{ + OpenALOutput *od = (OpenALOutput *)ao; + ALuint buffer; + + if (alcGetCurrentContext() != od->context) { + alcMakeContextCurrent(od->context); + } + + if (od->filled < NUM_BUFFERS) { + /* fill all buffers */ + buffer = od->buffers[od->filled]; + od->filled++; + } else { + /* wait for processed buffer */ + while (!openal_has_processed(od)) + g_usleep(10); + + alSourceUnqueueBuffers(od->source, 1, &buffer); + } + + alBufferData(buffer, od->format, chunk, size, od->frequency); + alSourceQueueBuffers(od->source, 1, &buffer); + + if (!openal_is_playing(od)) + alSourcePlay(od->source); + + return size; +} + +static void +openal_cancel(struct audio_output *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + + /* force-unqueue all buffers */ + alSourcei(od->source, AL_BUFFER, 0); + od->filled = 0; +} + +const struct audio_output_plugin openal_output_plugin = { + "openal", + nullptr, + openal_init, + openal_finish, + nullptr, + nullptr, + openal_open, + openal_close, + openal_delay, + nullptr, + openal_play, + nullptr, + openal_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/OpenALOutputPlugin.hxx new file mode 100644 index 000000000..e1ebf3d4f --- /dev/null +++ b/src/output/OpenALOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX +#define MPD_OPENAL_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin openal_output_plugin; + +#endif diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx new file mode 100644 index 000000000..781e2bf43 --- /dev/null +++ b/src/output/OssOutputPlugin.cxx @@ -0,0 +1,776 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OssOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +/* We got bug reports from FreeBSD users who said that the two 24 bit + formats generate white noise on FreeBSD, but 32 bit works. This is + a workaround until we know what exactly is expected by the kernel + audio drivers. */ +#ifndef __linux__ +#undef AFMT_S24_PACKED +#undef AFMT_S24_NE +#endif + +#ifdef AFMT_S24_PACKED +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" +#endif + +struct OssOutput { + struct audio_output base; + +#ifdef AFMT_S24_PACKED + Manual<PcmExport> pcm_export; +#endif + + int fd; + const char *device; + + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + AudioFormat audio_format; + + /** + * The current OSS audio format. This is needed to reopen the + * device after cancel(). + */ + int oss_format; + + OssOutput():fd(-1), device(nullptr) {} + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &oss_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +static constexpr Domain oss_output_domain("oss_output"); + +enum oss_stat { + OSS_STAT_NO_ERROR = 0, + OSS_STAT_NOT_CHAR_DEV = -1, + OSS_STAT_NO_PERMS = -2, + OSS_STAT_DOESN_T_EXIST = -3, + OSS_STAT_OTHER = -4, +}; + +static enum oss_stat +oss_stat_device(const char *device, int *errno_r) +{ + struct stat st; + + if (0 == stat(device, &st)) { + if (!S_ISCHR(st.st_mode)) { + return OSS_STAT_NOT_CHAR_DEV; + } + } else { + *errno_r = errno; + + switch (errno) { + case ENOENT: + case ENOTDIR: + return OSS_STAT_DOESN_T_EXIST; + case EACCES: + return OSS_STAT_NO_PERMS; + default: + return OSS_STAT_OTHER; + } + } + + return OSS_STAT_NO_ERROR; +} + +static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; + +static bool +oss_output_test_default_device(void) +{ + int fd, i; + + for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { + close(fd); + return true; + } + + FormatErrno(oss_output_domain, + "Error opening OSS device \"%s\"", + default_devices[i]); + } + + return false; +} + +static struct audio_output * +oss_open_default(Error &error) +{ + int err[G_N_ELEMENTS(default_devices)]; + enum oss_stat ret[G_N_ELEMENTS(default_devices)]; + + const config_param empty; + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + ret[i] = oss_stat_device(default_devices[i], &err[i]); + if (ret[i] == OSS_STAT_NO_ERROR) { + OssOutput *od = new OssOutput(); + if (!od->Initialize(empty, error)) { + delete od; + return NULL; + } + + od->device = default_devices[i]; + return &od->base; + } + } + + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + const char *dev = default_devices[i]; + switch(ret[i]) { + case OSS_STAT_NO_ERROR: + /* never reached */ + break; + case OSS_STAT_DOESN_T_EXIST: + FormatWarning(oss_output_domain, + "%s not found", dev); + break; + case OSS_STAT_NOT_CHAR_DEV: + FormatWarning(oss_output_domain, + "%s is not a character device", dev); + break; + case OSS_STAT_NO_PERMS: + FormatWarning(oss_output_domain, + "%s: permission denied", dev); + break; + case OSS_STAT_OTHER: + FormatErrno(oss_output_domain, err[i], + "Error accessing %s", dev); + } + } + + error.Set(oss_output_domain, + "error trying to open default OSS device"); + return NULL; +} + +static struct audio_output * +oss_output_init(const config_param ¶m, Error &error) +{ + const char *device = param.GetBlockValue("device"); + if (device != NULL) { + OssOutput *od = new OssOutput(); + if (!od->Initialize(param, error)) { + delete od; + return NULL; + } + + od->device = device; + return &od->base; + } + + return oss_open_default(error); +} + +static void +oss_output_finish(struct audio_output *ao) +{ + OssOutput *od = (OssOutput *)ao; + + ao_base_finish(&od->base); + delete od; +} + +#ifdef AFMT_S24_PACKED + +static bool +oss_output_enable(struct audio_output *ao, gcc_unused Error &error) +{ + OssOutput *od = (OssOutput *)ao; + + od->pcm_export.Construct(); + return true; +} + +static void +oss_output_disable(struct audio_output *ao) +{ + OssOutput *od = (OssOutput *)ao; + + od->pcm_export.Destruct(); +} + +#endif + +static void +oss_close(OssOutput *od) +{ + if (od->fd >= 0) + close(od->fd); + od->fd = -1; +} + +/** + * A tri-state type for oss_try_ioctl(). + */ +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * 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 an #Error. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, Error &error) +{ + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(!error.IsDefined()); + + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; + + if (errno == EINVAL) + return UNSUPPORTED; + + error.SetErrno(msg); + return ERROR; +} + +/** + * 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 an #Error. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, Error &error_r) +{ + return oss_try_ioctl_r(fd, request, &value, msg, error_r); +} + +/** + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. + */ +static bool +oss_setup_channels(int fd, AudioFormat &audio_format, Error &error) +{ + 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); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format.channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + 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); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format.channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + error.Set(oss_output_domain, 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, AudioFormat &audio_format, + Error &error) +{ + 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); + 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); + 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; + } + } + + error.Set(oss_output_domain, 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(SampleFormat format) +{ + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + return AFMT_QUERY; + + case SampleFormat::S8: + return AFMT_S8; + + case SampleFormat::S16: + return AFMT_S16_NE; + + case SampleFormat::S24_P32: +#ifdef AFMT_S24_NE + return AFMT_S24_NE; +#else + return AFMT_QUERY; +#endif + + case SampleFormat::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 + * SampleFormat::UNDEFINED if there is no direct counterpart. + */ +static SampleFormat +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SampleFormat::S8; + + case AFMT_S16_NE: + return SampleFormat::S16; + +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: + return SampleFormat::S24_P32; +#endif + +#ifdef AFMT_S24_NE + case AFMT_S24_NE: + return SampleFormat::S24_P32; +#endif + +#ifdef AFMT_S32_NE + case AFMT_S32_NE: + return SampleFormat::S32; +#endif + + default: + return SampleFormat::UNDEFINED; + } +} + +/** + * Probe one sample format. + * + * @return the selected sample format or SampleFormat::UNDEFINED on + * error + */ +static enum oss_setup_result +oss_probe_sample_format(int fd, SampleFormat sample_format, + SampleFormat *sample_format_r, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + PcmExport &pcm_export, +#endif + Error &error) +{ + int oss_format = sample_format_to_oss(sample_format); + if (oss_format == AFMT_QUERY) + return UNSUPPORTED; + + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error); + +#ifdef AFMT_S24_PACKED + if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) { + /* if the driver doesn't support padded 24 bit, try + packed 24 bit */ + oss_format = AFMT_S24_PACKED; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error); + } +#endif + + if (result != SUCCESS) + return result; + + sample_format = sample_format_from_oss(oss_format); + if (sample_format == SampleFormat::UNDEFINED) + return UNSUPPORTED; + + *sample_format_r = sample_format; + *oss_format_r = oss_format; + +#ifdef AFMT_S24_PACKED + pcm_export.Open(sample_format, 0, false, false, + oss_format == AFMT_S24_PACKED, + oss_format == AFMT_S24_PACKED && + G_BYTE_ORDER != G_LITTLE_ENDIAN); +#endif + + return SUCCESS; +} + +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, AudioFormat &audio_format, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + PcmExport &pcm_export, +#endif + Error &error) +{ + SampleFormat mpd_format; + enum oss_setup_result result = + oss_probe_sample_format(fd, audio_format.format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error); + switch (result) { + case SUCCESS: + audio_format.format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + if (result != UNSUPPORTED) + return result == SUCCESS; + + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const SampleFormat sample_formats[] = { + SampleFormat::S24_P32, + SampleFormat::S32, + SampleFormat::S16, + SampleFormat::S8, + SampleFormat::UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format.format) + /* don't try that again */ + continue; + + result = oss_probe_sample_format(fd, mpd_format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error); + switch (result) { + case SUCCESS: + audio_format.format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + error.Set(oss_output_domain, "Failed to set sample format"); + return false; +} + +/** + * Sets up the OSS device which was opened before. + */ +static bool +oss_setup(OssOutput *od, AudioFormat &audio_format, + Error &error) +{ + return oss_setup_channels(od->fd, audio_format, error) && + oss_setup_sample_rate(od->fd, audio_format, error) && + oss_setup_sample_format(od->fd, audio_format, &od->oss_format, +#ifdef AFMT_S24_PACKED + od->pcm_export, +#endif + error); +} + +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(OssOutput *od, Error &error) +{ + assert(od->fd < 0); + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + error.FormatErrno("Error opening OSS device \"%s\"", + od->device); + return false; + } + + 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); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + error.Set(oss_output_domain, 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); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + error.Set(oss_output_domain, msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + od->oss_format, + msg3, error); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + error.Set(oss_output_domain, msg3); + return false; + } + + return true; +} + +static bool +oss_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + OssOutput *od = (OssOutput *)ao; + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + error.FormatErrno("Error opening OSS device \"%s\"", + od->device); + return false; + } + + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } + + od->audio_format = audio_format; + return true; +} + +static void +oss_output_close(struct audio_output *ao) +{ + OssOutput *od = (OssOutput *)ao; + + oss_close(od); +} + +static void +oss_output_cancel(struct audio_output *ao) +{ + OssOutput *od = (OssOutput *)ao; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static size_t +oss_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + OssOutput *od = (OssOutput *)ao; + ssize_t ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && !oss_reopen(od, error)) + return 0; + +#ifdef AFMT_S24_PACKED + chunk = od->pcm_export->Export(chunk, size, size); +#endif + + while (true) { + ret = write(od->fd, chunk, size); + if (ret > 0) { +#ifdef AFMT_S24_PACKED + ret = od->pcm_export->CalcSourceSize(ret); +#endif + return ret; + } + + if (ret < 0 && errno != EINTR) { + error.FormatErrno("Write error on %s", od->device); + return 0; + } + } +} + +const struct audio_output_plugin oss_output_plugin = { + "oss", + oss_output_test_default_device, + oss_output_init, + oss_output_finish, +#ifdef AFMT_S24_PACKED + oss_output_enable, + oss_output_disable, +#else + nullptr, + nullptr, +#endif + oss_output_open, + oss_output_close, + nullptr, + nullptr, + oss_output_play, + nullptr, + oss_output_cancel, + nullptr, + + &oss_mixer_plugin, +}; diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx new file mode 100644 index 000000000..6c5c9530b --- /dev/null +++ b/src/output/OssOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX +#define MPD_OSS_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin oss_output_plugin; + +#endif diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx new file mode 100644 index 000000000..2b830ef29 --- /dev/null +++ b/src/output/PipeOutputPlugin.cxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PipeOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <stdio.h> +#include <errno.h> + +struct PipeOutput { + struct audio_output base; + + char *cmd; + FILE *fh; + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &pipe_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain pipe_output_domain("pipe_output"); + +inline bool +PipeOutput::Configure(const config_param ¶m, Error &error) +{ + cmd = param.DupBlockString("command"); + if (cmd == nullptr) { + error.Set(config_domain, + "No \"command\" parameter specified"); + return false; + } + + return true; +} + +static struct audio_output * +pipe_output_init(const config_param ¶m, Error &error) +{ + PipeOutput *pd = new PipeOutput(); + + if (!pd->Initialize(param, error)) { + delete pd; + return nullptr; + } + + if (!pd->Configure(param, error)) { + pd->Deinitialize(); + delete pd; + return nullptr; + } + + return &pd->base; +} + +static void +pipe_output_finish(struct audio_output *ao) +{ + PipeOutput *pd = (PipeOutput *)ao; + + g_free(pd->cmd); + pd->Deinitialize(); + delete pd; +} + +static bool +pipe_output_open(struct audio_output *ao, + gcc_unused AudioFormat &audio_format, + Error &error) +{ + PipeOutput *pd = (PipeOutput *)ao; + + pd->fh = popen(pd->cmd, "w"); + if (pd->fh == nullptr) { + error.FormatErrno("Error opening pipe \"%s\"", + pd->cmd); + return false; + } + + return true; +} + +static void +pipe_output_close(struct audio_output *ao) +{ + PipeOutput *pd = (PipeOutput *)ao; + + pclose(pd->fh); +} + +static size_t +pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + PipeOutput *pd = (PipeOutput *)ao; + size_t ret; + + ret = fwrite(chunk, 1, size, pd->fh); + if (ret == 0) + error.SetErrno("Write error on pipe"); + + return ret; +} + +const struct audio_output_plugin pipe_output_plugin = { + "pipe", + nullptr, + pipe_output_init, + pipe_output_finish, + nullptr, + nullptr, + pipe_output_open, + pipe_output_close, + nullptr, + nullptr, + pipe_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/PipeOutputPlugin.hxx new file mode 100644 index 000000000..f0c29706b --- /dev/null +++ b/src/output/PipeOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX +#define MPD_PIPE_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin pipe_output_plugin; + +#endif diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx new file mode 100644 index 000000000..ab797387d --- /dev/null +++ b/src/output/PulseOutputPlugin.cxx @@ -0,0 +1,889 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PulseOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "mixer/PulseMixerPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#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 <pulse/version.h> + +#include <assert.h> +#include <stddef.h> + +#define MPD_PULSE_NAME "Music Player Daemon" + +struct PulseOutput { + struct audio_output base; + + const char *name; + const char *server; + const char *sink; + + PulseMixer *mixer; + + struct pa_threaded_mainloop *mainloop; + struct pa_context *context; + struct pa_stream *stream; + + size_t writable; +}; + +static constexpr Domain pulse_output_domain("pulse_output"); + +static void +SetError(Error &error, pa_context *context, const char *msg) +{ + const int e = pa_context_errno(context); + error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e)); +} + +void +pulse_output_lock(PulseOutput *po) +{ + pa_threaded_mainloop_lock(po->mainloop); +} + +void +pulse_output_unlock(PulseOutput *po) +{ + pa_threaded_mainloop_unlock(po->mainloop); +} + +void +pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm) +{ + assert(po != nullptr); + assert(po->mixer == nullptr); + assert(pm != nullptr); + + po->mixer = pm; + + if (po->mainloop == nullptr) + return; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context != nullptr && + pa_context_get_state(po->context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(pm, po->context); + + if (po->stream != nullptr && + 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(PulseOutput *po, gcc_unused PulseMixer *pm) +{ + assert(po != nullptr); + assert(pm != nullptr); + assert(po->mixer == pm); + + po->mixer = nullptr; +} + +bool +pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume, + Error &error) +{ + pa_operation *o; + + if (po->context == nullptr || po->stream == nullptr || + pa_stream_get_state(po->stream) != PA_STREAM_READY) { + error.Set(pulse_output_domain, "disconnected"); + return false; + } + + o = pa_context_set_sink_input_volume(po->context, + pa_stream_get_index(po->stream), + volume, nullptr, nullptr); + if (o == nullptr) { + SetError(error, po->context, + "failed to set PulseAudio volume"); + 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 != nullptr); + assert(operation != nullptr); + + 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(gcc_unused pa_stream *s, + gcc_unused int success, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + if (po->mixer != nullptr) + 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 != nullptr) + 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) +{ + PulseOutput *po = (PulseOutput *)userdata; + pa_subscription_event_type_t facility = + pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + pa_subscription_event_type_t type = + pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + + if (po->mixer != nullptr && + facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && + po->stream != nullptr && + 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(PulseOutput *po, Error &error) +{ + assert(po != nullptr); + assert(po->context != nullptr); + + if (pa_context_connect(po->context, po->server, + (pa_context_flags_t)0, nullptr) < 0) { + SetError(error, po->context, + "pa_context_connect() has failed"); + return false; + } + + return true; +} + +/** + * Frees and clears the stream. + */ +static void +pulse_output_delete_stream(PulseOutput *po) +{ + assert(po != nullptr); + assert(po->stream != nullptr); + + pa_stream_set_suspended_callback(po->stream, nullptr, nullptr); + + pa_stream_set_state_callback(po->stream, nullptr, nullptr); + pa_stream_set_write_callback(po->stream, nullptr, nullptr); + + pa_stream_disconnect(po->stream); + pa_stream_unref(po->stream); + po->stream = nullptr; +} + +/** + * Frees and clears the context. + * + * Caller must lock the main loop. + */ +static void +pulse_output_delete_context(PulseOutput *po) +{ + assert(po != nullptr); + assert(po->context != nullptr); + + pa_context_set_state_callback(po->context, nullptr, nullptr); + pa_context_set_subscribe_callback(po->context, nullptr, nullptr); + + pa_context_disconnect(po->context); + pa_context_unref(po->context); + po->context = nullptr; +} + +/** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_context(PulseOutput *po, Error &error) +{ + assert(po != nullptr); + assert(po->mainloop != nullptr); + + po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), + MPD_PULSE_NAME); + if (po->context == nullptr) { + error.Set(pulse_output_domain, "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)) { + pulse_output_delete_context(po); + return false; + } + + return true; +} + +static struct audio_output * +pulse_output_init(const config_param ¶m, Error &error) +{ + PulseOutput *po; + + g_setenv("PULSE_PROP_media.role", "music", true); + + po = new PulseOutput(); + if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) { + delete po; + return nullptr; + } + + po->name = param.GetBlockValue("name", "mpd_pulse"); + po->server = param.GetBlockValue("server"); + po->sink = param.GetBlockValue("sink"); + + po->mixer = nullptr; + po->mainloop = nullptr; + po->context = nullptr; + po->stream = nullptr; + + return &po->base; +} + +static void +pulse_output_finish(struct audio_output *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + + ao_base_finish(&po->base); + delete po; +} + +static bool +pulse_output_enable(struct audio_output *ao, Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop == nullptr); + assert(po->context == nullptr); + + /* create the libpulse mainloop and start the thread */ + + po->mainloop = pa_threaded_mainloop_new(); + if (po->mainloop == nullptr) { + g_free(po); + + error.Set(pulse_output_domain, + "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 = nullptr; + + error.Set(pulse_output_domain, + "pa_threaded_mainloop_start() has failed"); + return false; + } + + /* create the libpulse context and connect it */ + + if (!pulse_output_setup_context(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_stop(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = nullptr; + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_disable(struct audio_output *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop != nullptr); + + pa_threaded_mainloop_stop(po->mainloop); + if (po->context != nullptr) + pulse_output_delete_context(po); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = nullptr; +} + +/** + * Check if the context is (already) connected, and waits if not. If + * the context has been disconnected, retry to connect. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_connection(PulseOutput *po, Error &error) +{ + assert(po->mainloop != nullptr); + + pa_context_state_t state; + + if (po->context == nullptr && !pulse_output_setup_context(po, error)) + return false; + + while (true) { + state = pa_context_get_state(po->context); + switch (state) { + case PA_CONTEXT_READY: + /* nothing to do */ + return true; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* failure */ + SetError(error, po->context, "failed to connect"); + pulse_output_delete_context(po); + 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_suspended_cb(gcc_unused pa_stream *stream, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + assert(stream == po->stream || po->stream == nullptr); + assert(po->mainloop != nullptr); + + /* wake up the main loop to break out of the loop in + pulse_output_play() */ + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + assert(stream == po->stream || po->stream == nullptr); + assert(po->mainloop != nullptr); + assert(po->context != nullptr); + + switch (pa_stream_get_state(stream)) { + case PA_STREAM_READY: + if (po->mixer != nullptr) + 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 != nullptr) + 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(gcc_unused pa_stream *stream, size_t nbytes, + void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + assert(po->mainloop != nullptr); + + po->writable = nbytes; + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +/** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, + Error &error) +{ + assert(po != nullptr); + assert(po->context != nullptr); + + po->stream = pa_stream_new(po->context, po->name, ss, nullptr); + if (po->stream == nullptr) { + SetError(error, po->context, "pa_stream_new() has failed"); + return false; + } + + pa_stream_set_suspended_callback(po->stream, + pulse_output_stream_suspended_cb, po); + + pa_stream_set_state_callback(po->stream, + pulse_output_stream_state_cb, po); + pa_stream_set_write_callback(po->stream, + pulse_output_stream_write_cb, po); + + return true; +} + +static bool +pulse_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + pa_sample_spec ss; + + assert(po->mainloop != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context != nullptr) { + 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)) { + pa_threaded_mainloop_unlock(po->mainloop); + 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 = SampleFormat::S16; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audio_format.sample_rate; + ss.channels = audio_format.channels; + + /* create a stream .. */ + + if (!pulse_output_setup_stream(po, &ss, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + /* .. and connect it (asynchronously) */ + + if (pa_stream_connect_playback(po->stream, po->sink, + nullptr, pa_stream_flags_t(0), + nullptr, nullptr) < 0) { + pulse_output_delete_stream(po); + + SetError(error, po->context, + "pa_stream_connect_playback() has failed"); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_close(struct audio_output *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + pa_operation *o; + + assert(po->mainloop != nullptr); + + 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 == nullptr) { + FormatWarning(pulse_output_domain, + "pa_stream_drain() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + } else + pulse_wait_for_operation(po->mainloop, o); + } + + pulse_output_delete_stream(po); + + if (po->context != nullptr && + 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 if not. The + * mainloop must be locked before calling this function. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_stream(PulseOutput *po, Error &error) +{ + while (true) { + switch (pa_stream_get_state(po->stream)) { + case PA_STREAM_READY: + return true; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + SetError(error, po->context, + "failed to connect the stream"); + return false; + + case PA_STREAM_CREATING: + pa_threaded_mainloop_wait(po->mainloop); + break; + } + } +} + +/** + * Sets cork mode on the stream. + */ +static bool +pulse_output_stream_pause(PulseOutput *po, bool pause, + Error &error) +{ + pa_operation *o; + + assert(po->mainloop != nullptr); + assert(po->context != nullptr); + assert(po->stream != nullptr); + + o = pa_stream_cork(po->stream, pause, + pulse_output_stream_success_cb, po); + if (o == nullptr) { + SetError(error, po->context, "pa_stream_cork() has failed"); + return false; + } + + if (!pulse_wait_for_operation(po->mainloop, o)) { + SetError(error, po->context, "pa_stream_cork() has failed"); + return false; + } + + return true; +} + +static unsigned +pulse_output_delay(struct audio_output *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + unsigned result = 0; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->base.pause && pa_stream_is_corked(po->stream) && + pa_stream_get_state(po->stream) == PA_STREAM_READY) + /* idle while paused */ + result = 1000; + + pa_threaded_mainloop_unlock(po->mainloop); + + return result; +} + +static size_t +pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already) connected */ + + if (!pulse_output_wait_stream(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + assert(po->context != nullptr); + + /* unpause if previously paused */ + + if (pa_stream_is_corked(po->stream) && + !pulse_output_stream_pause(po, false, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + /* wait until the server allows us to write */ + + while (po->writable == 0) { + if (pa_stream_is_suspended(po->stream)) { + pa_threaded_mainloop_unlock(po->mainloop); + error.Set(pulse_output_domain, "suspended"); + return 0; + } + + pa_threaded_mainloop_wait(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(po->mainloop); + error.Set(pulse_output_domain, "disconnected"); + return 0; + } + } + + /* now write */ + + if (size > po->writable) + /* don't send more than possible */ + size = po->writable; + + po->writable -= size; + + int result = pa_stream_write(po->stream, chunk, size, nullptr, + 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(po->mainloop); + if (result < 0) { + SetError(error, po->context, "pa_stream_write() failed"); + return 0; + } + + return size; +} + +static void +pulse_output_cancel(struct audio_output *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + pa_operation *o; + + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); + + 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 != nullptr); + + o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + if (o == nullptr) { + FormatWarning(pulse_output_domain, + "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(struct audio_output *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already/still) connected */ + + Error error; + if (!pulse_output_wait_stream(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + LogError(error); + return false; + } + + assert(po->context != nullptr); + + /* cork the stream */ + + if (!pa_stream_is_corked(po->stream) && + !pulse_output_stream_pause(po, true, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + LogError(error); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static bool +pulse_output_test_default_device(void) +{ + bool success; + + const config_param empty; + PulseOutput *po = (PulseOutput *) + pulse_output_init(empty, IgnoreError()); + if (po == nullptr) + return false; + + success = pulse_output_wait_connection(po, IgnoreError()); + pulse_output_finish(&po->base); + + return success; +} + +const struct audio_output_plugin pulse_output_plugin = { + "pulse", + pulse_output_test_default_device, + pulse_output_init, + pulse_output_finish, + pulse_output_enable, + pulse_output_disable, + pulse_output_open, + pulse_output_close, + pulse_output_delay, + nullptr, + pulse_output_play, + nullptr, + pulse_output_cancel, + pulse_output_pause, + + &pulse_mixer_plugin, +}; diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx new file mode 100644 index 000000000..0ed8404bc --- /dev/null +++ b/src/output/PulseOutputPlugin.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_PULSE_OUTPUT_PLUGIN_HXX + +struct PulseOutput; +struct PulseMixer; +struct pa_cvolume; +class Error; + +extern const struct audio_output_plugin pulse_output_plugin; + +void +pulse_output_lock(PulseOutput *po); + +void +pulse_output_unlock(PulseOutput *po); + +void +pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm); + +void +pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm); + +bool +pulse_output_set_volume(PulseOutput *po, + const struct pa_cvolume *volume, Error &error); + +#endif diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx new file mode 100644 index 000000000..9a7eba01f --- /dev/null +++ b/src/output/RecorderOutputPlugin.cxx @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "RecorderOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "EncoderPlugin.hxx" +#include "EncoderList.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/fd_util.h" +#include "open.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +struct RecorderOutput { + struct audio_output base; + + /** + * The configured encoder plugin. + */ + Encoder *encoder; + + /** + * The destination file name. + */ + const char *path; + + /** + * The destination file descriptor. + */ + int fd; + + /** + * The buffer for encoder_read(). + */ + char buffer[32768]; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &recorder_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); + + bool WriteToFile(const void *data, size_t length, Error &error); + + /** + * Writes pending data from the encoder to the output file. + */ + bool EncoderToFile(Error &error); +}; + +static constexpr Domain recorder_output_domain("recorder_output"); + +inline bool +RecorderOutput::Configure(const config_param ¶m, Error &error) +{ + /* read configuration */ + + const char *encoder_name = + param.GetBlockValue("encoder", "vorbis"); + const auto encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == nullptr) { + error.Format(config_domain, + "No such encoder: %s", encoder_name); + return false; + } + + path = param.GetBlockValue("path"); + if (path == nullptr) { + error.Set(config_domain, "'path' not configured"); + return false; + } + + /* initialize encoder */ + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + return true; +} + +static audio_output * +recorder_output_init(const config_param ¶m, Error &error) +{ + RecorderOutput *recorder = new RecorderOutput(); + + if (!recorder->Initialize(param, error)) { + delete recorder; + return nullptr; + } + + if (!recorder->Configure(param, error)) { + recorder->Deinitialize(); + delete recorder; + return nullptr; + } + + return &recorder->base; +} + +static void +recorder_output_finish(struct audio_output *ao) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + encoder_finish(recorder->encoder); + recorder->Deinitialize(); + delete recorder; +} + +inline bool +RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error) +{ + assert(length > 0); + + const uint8_t *data = (const uint8_t *)_data, *end = data + length; + + while (true) { + ssize_t nbytes = write(fd, data, end - data); + if (nbytes > 0) { + data += nbytes; + if (data == end) + return true; + } else if (nbytes == 0) { + /* shouldn't happen for files */ + error.Set(recorder_output_domain, + "write() returned 0"); + return false; + } else if (errno != EINTR) { + error.FormatErrno("Failed to write to '%s'", path); + return false; + } + } +} + +inline bool +RecorderOutput::EncoderToFile(Error &error) +{ + assert(fd >= 0); + + while (true) { + /* read from the encoder */ + + size_t size = encoder_read(encoder, buffer, sizeof(buffer)); + if (size == 0) + return true; + + /* write everything into the file */ + + if (!WriteToFile(buffer, size, error)) + return false; + } +} + +static bool +recorder_output_open(struct audio_output *ao, + AudioFormat &audio_format, + Error &error) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + /* create the output file */ + + recorder->fd = open_cloexec(recorder->path, + O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, + 0666); + if (recorder->fd < 0) { + error.FormatErrno("Failed to create '%s'", recorder->path); + return false; + } + + /* open the encoder */ + + if (!encoder_open(recorder->encoder, audio_format, error)) { + close(recorder->fd); + unlink(recorder->path); + return false; + } + + if (!recorder->EncoderToFile(error)) { + encoder_close(recorder->encoder); + close(recorder->fd); + unlink(recorder->path); + return false; + } + + return true; +} + +static void +recorder_output_close(struct audio_output *ao) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + /* flush the encoder and write the rest to the file */ + + if (encoder_end(recorder->encoder, IgnoreError())) + recorder->EncoderToFile(IgnoreError()); + + /* now really close everything */ + + encoder_close(recorder->encoder); + + close(recorder->fd); +} + +static size_t +recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + return encoder_write(recorder->encoder, chunk, size, error) && + recorder->EncoderToFile(error) + ? size : 0; +} + +const struct audio_output_plugin recorder_output_plugin = { + "recorder", + nullptr, + recorder_output_init, + recorder_output_finish, + nullptr, + nullptr, + recorder_output_open, + recorder_output_close, + nullptr, + nullptr, + recorder_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/RecorderOutputPlugin.hxx new file mode 100644 index 000000000..a27f51e23 --- /dev/null +++ b/src/output/RecorderOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX +#define MPD_RECORDER_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin recorder_output_plugin; + +#endif diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx new file mode 100644 index 000000000..9d66bb63b --- /dev/null +++ b/src/output/RoarOutputPlugin.cxx @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "RoarOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +/* libroar/services.h declares roar_service_stream::new - work around + this C++ problem */ +#define new _new +#include <roaraudio.h> +#undef new + +struct RoarOutput { + struct audio_output base; + + roar_vs_t * vss; + int err; + char *host; + char *name; + int role; + struct roar_connection con; + struct roar_audio_info info; + Mutex mutex; + volatile bool alive; + + RoarOutput() + :err(ROAR_ERROR_NONE), + host(nullptr), name(nullptr) {} + + ~RoarOutput() { + g_free(host); + g_free(name); + } +}; + +static constexpr Domain roar_output_domain("roar_output"); + +static int +roar_output_get_volume_locked(RoarOutput *roar) +{ + if (roar->vss == nullptr || !roar->alive) + return -1; + + float l, r; + int error; + if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) + return -1; + + return (l + r) * 50; +} + +int +roar_output_get_volume(RoarOutput *roar) +{ + const ScopeLock protect(roar->mutex); + return roar_output_get_volume_locked(roar); +} + +static bool +roar_output_set_volume_locked(RoarOutput *roar, unsigned volume) +{ + assert(volume <= 100); + + if (roar->vss == nullptr || !roar->alive) + return false; + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(roar->vss, level, &error); + return true; +} + +bool +roar_output_set_volume(RoarOutput *roar, unsigned volume) +{ + const ScopeLock protect(roar->mutex); + return roar_output_set_volume_locked(roar, volume); +} + +static void +roar_configure(RoarOutput *self, const config_param ¶m) +{ + self->host = param.DupBlockString("server", nullptr); + self->name = param.DupBlockString("name", "MPD"); + + const char *role = param.GetBlockValue("role", "music"); + self->role = role != nullptr + ? roar_str2role(role) + : ROAR_ROLE_MUSIC; +} + +static struct audio_output * +roar_init(const config_param ¶m, Error &error) +{ + RoarOutput *self = new RoarOutput(); + + if (!ao_base_init(&self->base, &roar_output_plugin, param, error)) { + delete self; + return nullptr; + } + + roar_configure(self, param); + return &self->base; +} + +static void +roar_finish(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + ao_base_finish(&self->base); + delete self; +} + +static void +roar_use_audio_format(struct roar_audio_info *info, + AudioFormat &audio_format) +{ + info->rate = audio_format.sample_rate; + info->channels = audio_format.channels; + info->codec = ROAR_CODEC_PCM_S; + + switch (audio_format.format) { + case SampleFormat::UNDEFINED: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + info->bits = 16; + audio_format.format = SampleFormat::S16; + break; + + case SampleFormat::S8: + info->bits = 8; + break; + + case SampleFormat::S16: + info->bits = 16; + break; + + case SampleFormat::S24_P32: + info->bits = 32; + audio_format.format = SampleFormat::S32; + break; + + case SampleFormat::S32: + info->bits = 32; + break; + } +} + +static bool +roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) +{ + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); + + if (roar_simple_connect(&(self->con), self->host, self->name) < 0) + { + error.Set(roar_output_domain, + "Failed to connect to Roar server"); + return false; + } + + self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); + + if (self->vss == nullptr || self->err != ROAR_ERROR_NONE) + { + error.Set(roar_output_domain, "Failed to connect to server"); + return false; + } + + roar_use_audio_format(&self->info, audio_format); + + if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) + { + error.Set(roar_output_domain, "Failed to start stream"); + return false; + } + roar_vs_role(self->vss, self->role, &(self->err)); + self->alive = true; + + return true; +} + +static void +roar_close(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); + + self->alive = false; + + if (self->vss != nullptr) + roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); + self->vss = nullptr; + roar_disconnect(&(self->con)); +} + +static void +roar_cancel_locked(RoarOutput *self) +{ + if (self->vss == nullptr) + return; + + roar_vs_t *vss = self->vss; + self->vss = nullptr; + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + self->alive = false; + + vss = roar_vs_new_from_con(&(self->con), &(self->err)); + if (vss == nullptr) + return; + + if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) { + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + LogError(roar_output_domain, "Failed to start stream"); + return; + } + + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; +} + +static void +roar_cancel(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + const ScopeLock protect(self->mutex); + roar_cancel_locked(self); +} + +static size_t +roar_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + RoarOutput *self = (RoarOutput *)ao; + ssize_t rc; + + if (self->vss == nullptr) + { + error.Set(roar_output_domain, "Connection is invalid"); + return 0; + } + + rc = roar_vs_write(self->vss, chunk, size, &(self->err)); + if ( rc <= 0 ) + { + error.Set(roar_output_domain, "Failed to play data"); + return 0; + } + + return rc; +} + +static const char* +roar_tag_convert(enum tag_type type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return nullptr; + } +} + +static void +roar_send_tag(struct audio_output *ao, const Tag *meta) +{ + RoarOutput *self = (RoarOutput *)ao; + + if (self->vss == nullptr) + return; + + const ScopeLock protect(self->mutex); + + size_t cnt = 1; + struct roar_keyval vals[32]; + memset(vals, 0, sizeof(vals)); + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); + + vals[0].key = g_strdup("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); + if (key != nullptr) + { + if (is_uuid) + { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + meta->items[i]->value); + vals[cnt].key = g_strdup(key); + vals[cnt].value = uuid_buf[cnt]; + } + else + { + vals[cnt].key = g_strdup(key); + vals[cnt].value = meta->items[i]->value; + } + cnt++; + } + } + + roar_vs_meta(self->vss, vals, cnt, &(self->err)); + + for (unsigned i = 0; i < 32; i++) + g_free(vals[i].key); +} + +const struct audio_output_plugin roar_output_plugin = { + "roar", + nullptr, + roar_init, + roar_finish, + nullptr, + nullptr, + roar_open, + roar_close, + nullptr, + roar_send_tag, + roar_play, + nullptr, + roar_cancel, + nullptr, + &roar_mixer_plugin, +}; diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/RoarOutputPlugin.hxx new file mode 100644 index 000000000..faa4b4d5c --- /dev/null +++ b/src/output/RoarOutputPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ROAR_OUTPUT_PLUGIN_H +#define MPD_ROAR_OUTPUT_PLUGIN_H + +struct RoarOutput; + +extern const struct audio_output_plugin roar_output_plugin; + +int +roar_output_get_volume(RoarOutput *roar); + +bool +roar_output_set_volume(RoarOutput *roar, unsigned volume); + +#endif diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx new file mode 100644 index 000000000..19f2b61cd --- /dev/null +++ b/src/output/ShoutOutputPlugin.cxx @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ShoutOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "EncoderPlugin.hxx" +#include "EncoderList.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <shout/shout.h> +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2; + +struct ShoutOutput final { + struct audio_output base; + + shout_t *shout_conn; + shout_metadata_t *shout_meta; + + Encoder *encoder; + + float quality; + int bitrate; + + int timeout; + + uint8_t buffer[32768]; + + ShoutOutput() + :shout_conn(shout_new()), + shout_meta(shout_metadata_new()), + quality(-2.0), + bitrate(-1), + timeout(DEFAULT_CONN_TIMEOUT) {} + + ~ShoutOutput() { + if (shout_meta != nullptr) + shout_metadata_free(shout_meta); + if (shout_conn != nullptr) + shout_free(shout_conn); + } + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &shout_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static int shout_init_count; + +static constexpr Domain shout_output_domain("shout_output"); + +static const EncoderPlugin * +shout_encoder_plugin_get(const char *name) +{ + if (strcmp(name, "ogg") == 0) + name = "vorbis"; + else if (strcmp(name, "mp3") == 0) + name = "lame"; + + return encoder_plugin_get(name); +} + +gcc_pure +static const char * +require_block_string(const config_param ¶m, const char *name) +{ + const char *value = param.GetBlockValue(name); + if (value == nullptr) + FormatFatalError("no \"%s\" defined for shout device defined " + "at line %u\n", name, param.line); + + return value; +} + +inline bool +ShoutOutput::Configure(const config_param ¶m, Error &error) +{ + + const AudioFormat audio_format = base.config_audio_format; + if (!audio_format.IsFullyDefined()) { + error.Set(config_domain, + "Need full audio format specification"); + return nullptr; + } + + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); + unsigned port = param.GetBlockValue("port", 0u); + if (port == 0) { + error.Set(config_domain, "shout port must be configured"); + return false; + } + + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); + + bool is_public = param.GetBlockValue("public", false); + + const char *user = param.GetBlockValue("user", "source"); + + const char *value = param.GetBlockValue("quality"); + if (value != nullptr) { + char *test; + quality = strtod(value, &test); + + if (*test != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "shout quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are " + "both defined"); + return false; + } + } else { + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + char *test; + bitrate = strtol(value, &test, 10); + + if (*test != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate must be a positive integer"); + return false; + } + } + + const char *encoding = param.GetBlockValue("encoding", "ogg"); + const auto encoder_plugin = shout_encoder_plugin_get(encoding); + if (encoder_plugin == nullptr) { + error.Format(config_domain, + "couldn't find shout encoder plugin \"%s\"", + encoding); + return false; + } + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + unsigned shout_format; + if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) + shout_format = SHOUT_FORMAT_MP3; + else + shout_format = SHOUT_FORMAT_OGG; + + unsigned protocol; + value = param.GetBlockValue("protocol"); + if (value != nullptr) { + if (0 == strcmp(value, "shoutcast") && + 0 != strcmp(encoding, "mp3")) { + error.Format(config_domain, + "you cannot stream \"%s\" to shoutcast, use mp3", + encoding); + return false; + } else if (0 == strcmp(value, "shoutcast")) + protocol = SHOUT_PROTOCOL_ICY; + else if (0 == strcmp(value, "icecast1")) + protocol = SHOUT_PROTOCOL_XAUDIOCAST; + else if (0 == strcmp(value, "icecast2")) + protocol = SHOUT_PROTOCOL_HTTP; + else { + error.Format(config_domain, + "shout protocol \"%s\" is not \"shoutcast\" or " + "\"icecast1\"or \"icecast2\"", + value); + return false; + } + } else { + protocol = SHOUT_PROTOCOL_HTTP; + } + + if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS || + shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS || + shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS || + shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS || + shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS || + shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS || + shout_set_format(shout_conn, shout_format) + != SHOUTERR_SUCCESS || + shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS || + shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + /* optional paramters */ + timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT); + + value = param.GetBlockValue("genre"); + if (value != nullptr && shout_set_genre(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + value = param.GetBlockValue("description"); + if (value != nullptr && shout_set_description(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + value = param.GetBlockValue("url"); + if (value != nullptr && shout_set_url(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%u", audio_format.channels); + shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate); + + shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp); + + if (quality >= -1.0) { + snprintf(temp, sizeof(temp), "%2.2f", quality); + shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY, + temp); + } else { + snprintf(temp, sizeof(temp), "%d", bitrate); + shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE, + temp); + } + } + + return true; +} + +static struct audio_output * +my_shout_init_driver(const config_param ¶m, Error &error) +{ + ShoutOutput *sd = new ShoutOutput(); + if (!sd->Initialize(param, error)) { + delete sd; + return nullptr; + } + + if (!sd->Configure(param, error)) { + sd->Deinitialize(); + delete sd; + return nullptr; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; +} + +static bool +handle_shout_error(ShoutOutput *sd, int err, Error &error) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + error.Format(shout_output_domain, err, + "Lost shout connection to %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + + default: + error.Format(shout_output_domain, err, + "connection to %s:%i error: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } + + return true; +} + +static bool +write_page(ShoutOutput *sd, Error &error) +{ + assert(sd->encoder != nullptr); + + while (true) { + size_t nbytes = encoder_read(sd->encoder, + sd->buffer, sizeof(sd->buffer)); + if (nbytes == 0) + return true; + + int err = shout_send(sd->shout_conn, sd->buffer, nbytes); + if (!handle_shout_error(sd, err, error)) + return false; + } + + return true; +} + +static void close_shout_conn(ShoutOutput * sd) +{ + if (sd->encoder != nullptr) { + if (encoder_end(sd->encoder, IgnoreError())) + write_page(sd, IgnoreError()); + + encoder_close(sd->encoder); + } + + if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && + shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { + FormatWarning(shout_output_domain, + "problem closing connection to shout server: %s", + shout_get_error(sd->shout_conn)); + } +} + +static void +my_shout_finish_driver(struct audio_output *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + encoder_finish(sd->encoder); + + sd->Deinitialize(); + delete sd; + + shout_init_count--; + + if (shout_init_count == 0) + shout_shutdown(); +} + +static void +my_shout_drop_buffered_audio(struct audio_output *ao) +{ + gcc_unused + ShoutOutput *sd = (ShoutOutput *)ao; + + /* needs to be implemented for shout */ +} + +static void +my_shout_close_device(struct audio_output *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + close_shout_conn(sd); +} + +static bool +shout_connect(ShoutOutput *sd, Error &error) +{ + switch (shout_open(sd->shout_conn)) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + return true; + + default: + error.Format(shout_output_domain, + "problem opening connection to shout server %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } +} + +static bool +my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + if (!shout_connect(sd, error)) + return false; + + if (!encoder_open(sd->encoder, audio_format, error)) { + shout_close(sd->shout_conn); + return false; + } + + if (!write_page(sd, error)) { + encoder_close(sd->encoder); + shout_close(sd->shout_conn); + return false; + } + + return true; +} + +static unsigned +my_shout_delay(struct audio_output *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + int delay = shout_delay(sd->shout_conn); + if (delay < 0) + delay = 0; + + return delay; +} + +static size_t +my_shout_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + return encoder_write(sd->encoder, chunk, size, error) && + write_page(sd, error) + ? size + : 0; +} + +static bool +my_shout_pause(struct audio_output *ao) +{ + static char silence[1020]; + + return my_shout_play(ao, silence, sizeof(silence), IgnoreError()); +} + +static void +shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) +{ + char artist[size]; + char title[size]; + + artist[0] = 0; + title[0] = 0; + + for (unsigned i = 0; i < tag->num_items; i++) { + switch (tag->items[i]->type) { + case TAG_ARTIST: + strncpy(artist, tag->items[i]->value, size); + break; + case TAG_TITLE: + strncpy(title, tag->items[i]->value, size); + break; + + default: + break; + } + } + + snprintf(dest, size, "%s - %s", artist, title); +} + +static void my_shout_set_tag(struct audio_output *ao, + const Tag *tag) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + if (sd->encoder->plugin.tag != nullptr) { + /* encoder plugin supports stream tags */ + + Error error; + if (!encoder_pre_tag(sd->encoder, error) || + !write_page(sd, error) || + !encoder_tag(sd->encoder, tag, error)) { + LogError(error); + return; + } + } else { + /* no stream tag support: fall back to icy-metadata */ + char song[1024]; + shout_tag_to_metadata(tag, song, sizeof(song)); + + shout_metadata_add(sd->shout_meta, "song", song); + if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, + sd->shout_meta)) { + LogWarning(shout_output_domain, + "error setting shout metadata"); + } + } + + write_page(sd, IgnoreError()); +} + +const struct audio_output_plugin shout_output_plugin = { + "shout", + nullptr, + my_shout_init_driver, + my_shout_finish_driver, + nullptr, + nullptr, + my_shout_open_device, + my_shout_close_device, + my_shout_delay, + my_shout_set_tag, + my_shout_play, + nullptr, + my_shout_drop_buffered_audio, + my_shout_pause, + nullptr, +}; diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/ShoutOutputPlugin.hxx new file mode 100644 index 000000000..496b77975 --- /dev/null +++ b/src/output/ShoutOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX +#define MPD_SHOUT_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin shout_output_plugin; + +#endif diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx new file mode 100644 index 000000000..0836dc2e2 --- /dev/null +++ b/src/output/SolarisOutputPlugin.cxx @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SolarisOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" + +#include <sys/stropts.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef __sun +#include <sys/audio.h> +#else + +/* some fake declarations that allow build this plugin on systems + other than Solaris, just to see if it compiles */ + +#define AUDIO_GETINFO 0 +#define AUDIO_SETINFO 0 +#define AUDIO_ENCODING_LINEAR 0 + +struct audio_info { + struct { + unsigned sample_rate, channels, precision, encoding; + } play; +}; + +#endif + +struct SolarisOutput { + struct audio_output base; + + /* configuration */ + const char *device; + + int fd; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &solaris_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +static bool +solaris_output_test_default_device(void) +{ + struct stat st; + + return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) && + access("/dev/audio", W_OK) == 0; +} + +static struct audio_output * +solaris_output_init(const config_param ¶m, Error &error_r) +{ + SolarisOutput *so = new SolarisOutput(); + if (!so->Initialize(param, error_r)) { + delete so; + return nullptr; + } + + so->device = param.GetBlockValue("device", "/dev/audio"); + + return &so->base; +} + +static void +solaris_output_finish(struct audio_output *ao) +{ + SolarisOutput *so = (SolarisOutput *)ao; + + so->Deinitialize(); + delete so; +} + +static bool +solaris_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + SolarisOutput *so = (SolarisOutput *)ao; + struct audio_info info; + int ret, flags; + + /* support only 16 bit mono/stereo for now; nothing else has + been tested */ + audio_format.format = SampleFormat::S16; + + /* open the device in non-blocking mode */ + + so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0); + if (so->fd < 0) { + error.FormatErrno("Failed to open %s", + so->device); + return false; + } + + /* restore blocking mode */ + + flags = fcntl(so->fd, F_GETFL); + if (flags > 0 && (flags & O_NONBLOCK) != 0) + fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK); + + /* configure the audio device */ + + ret = ioctl(so->fd, AUDIO_GETINFO, &info); + if (ret < 0) { + error.SetErrno("AUDIO_GETINFO failed"); + close(so->fd); + return false; + } + + info.play.sample_rate = audio_format.sample_rate; + info.play.channels = audio_format.channels; + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + + ret = ioctl(so->fd, AUDIO_SETINFO, &info); + if (ret < 0) { + error.SetErrno("AUDIO_SETINFO failed"); + close(so->fd); + return false; + } + + return true; +} + +static void +solaris_output_close(struct audio_output *ao) +{ + SolarisOutput *so = (SolarisOutput *)ao; + + close(so->fd); +} + +static size_t +solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + SolarisOutput *so = (SolarisOutput *)ao; + ssize_t nbytes; + + nbytes = write(so->fd, chunk, size); + if (nbytes <= 0) { + error.SetErrno("Write failed"); + return 0; + } + + return nbytes; +} + +static void +solaris_output_cancel(struct audio_output *ao) +{ + SolarisOutput *so = (SolarisOutput *)ao; + + ioctl(so->fd, I_FLUSH); +} + +const struct audio_output_plugin solaris_output_plugin = { + "solaris", + solaris_output_test_default_device, + solaris_output_init, + solaris_output_finish, + nullptr, + nullptr, + solaris_output_open, + solaris_output_close, + nullptr, + nullptr, + solaris_output_play, + nullptr, + solaris_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/SolarisOutputPlugin.hxx new file mode 100644 index 000000000..d0fbd32c8 --- /dev/null +++ b/src/output/SolarisOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX +#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin solaris_output_plugin; + +#endif diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx new file mode 100644 index 000000000..d3f74dd44 --- /dev/null +++ b/src/output/WinmmOutputPlugin.cxx @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "WinmmOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "pcm/PcmBuffer.hxx" +#include "MixerList.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> + +struct WinmmBuffer { + PcmBuffer buffer; + + WAVEHDR hdr; +}; + +struct WinmmOutput { + struct audio_output base; + + UINT device_id; + HWAVEOUT handle; + + /** + * This event is triggered by Windows when a buffer is + * finished. + */ + HANDLE event; + + WinmmBuffer buffers[8]; + unsigned next_buffer; +}; + +static constexpr Domain winmm_output_domain("winmm_output"); + +HWAVEOUT +winmm_output_get_handle(WinmmOutput *output) +{ + return output->handle; +} + +static bool +winmm_output_test_default_device(void) +{ + return waveOutGetNumDevs() > 0; +} + +static bool +get_device_id(const char *device_name, UINT *device_id, Error &error) +{ + /* if device is not specified use wave mapper */ + if (device_name == nullptr) { + *device_id = WAVE_MAPPER; + return true; + } + + UINT numdevs = waveOutGetNumDevs(); + + /* check for device id */ + char *endptr; + UINT id = strtoul(device_name, &endptr, 0); + if (endptr > device_name && *endptr == 0) { + if (id >= numdevs) + goto fail; + *device_id = id; + return true; + } + + /* check for device name */ + for (UINT i = 0; i < numdevs; i++) { + WAVEOUTCAPS caps; + MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) + continue; + /* szPname is only 32 chars long, so it is often truncated. + Use partial match to work around this. */ + if (strstr(device_name, caps.szPname) == device_name) { + *device_id = i; + return true; + } + } + +fail: + error.Format(winmm_output_domain, + "device \"%s\" is not found", device_name); + return false; +} + +static struct audio_output * +winmm_output_init(const config_param ¶m, Error &error) +{ + WinmmOutput *wo = new WinmmOutput(); + if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) { + g_free(wo); + return nullptr; + } + + const char *device = param.GetBlockValue("device"); + if (!get_device_id(device, &wo->device_id, error)) { + ao_base_finish(&wo->base); + g_free(wo); + return nullptr; + } + + return &wo->base; +} + +static void +winmm_output_finish(struct audio_output *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + ao_base_finish(&wo->base); + delete wo; +} + +static bool +winmm_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + wo->event = CreateEvent(nullptr, false, false, nullptr); + if (wo->event == nullptr) { + error.Set(winmm_output_domain, "CreateEvent() failed"); + return false; + } + + switch (audio_format.format) { + case SampleFormat::S8: + case SampleFormat::S16: + break; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + case SampleFormat::UNDEFINED: + /* we havn't tested formats other than S16 */ + audio_format.format = SampleFormat::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.GetFrameSize(); + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.wBitsPerSample = audio_format.GetSampleSize() * 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); + error.Set(winmm_output_domain, "waveOutOpen() failed"); + return false; + } + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { + memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); + } + + wo->next_buffer = 0; + + return true; +} + +static void +winmm_output_close(struct audio_output *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) + wo->buffers[i].buffer.Clear(); + + waveOutClose(wo->handle); + + CloseHandle(wo->event); +} + +/** + * Copy data into a buffer, and prepare the wave header. + */ +static bool +winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, + const void *data, size_t size, + Error &error) +{ + void *dest = buffer->buffer.Get(size); + assert(dest != nullptr); + + memcpy(dest, data, size); + + memset(&buffer->hdr, 0, sizeof(buffer->hdr)); + buffer->hdr.lpData = (LPSTR)dest; + buffer->hdr.dwBufferLength = size; + + MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_output_domain, result, + "waveOutPrepareHeader() failed"); + return false; + } + + return true; +} + +/** + * Wait until the buffer is finished. + */ +static bool +winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, + Error &error) +{ + 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) { + error.Set(winmm_output_domain, result, + "waveOutUnprepareHeader() failed"); + return false; + } + + /* wait some more */ + WaitForSingleObject(wo->event, INFINITE); + } +} + +static size_t +winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + /* get the next buffer from the ring and prepare it */ + WinmmBuffer *buffer = &wo->buffers[wo->next_buffer]; + if (!winmm_drain_buffer(wo, buffer, error) || + !winmm_set_buffer(wo, buffer, chunk, size, error)) + 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)); + error.Set(winmm_output_domain, 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(WinmmOutput *wo, Error &error) +{ + for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) + return false; + + for (unsigned i = 0; i < wo->next_buffer; ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) + return false; + + return true; +} + +static void +winmm_stop(WinmmOutput *wo) +{ + waveOutReset(wo->handle); + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { + WinmmBuffer *buffer = &wo->buffers[i]; + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + } +} + +static void +winmm_output_drain(struct audio_output *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + if (!winmm_drain_all_buffers(wo, IgnoreError())) + winmm_stop(wo); +} + +static void +winmm_output_cancel(struct audio_output *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + winmm_stop(wo); +} + +const struct audio_output_plugin winmm_output_plugin = { + "winmm", + winmm_output_test_default_device, + winmm_output_init, + winmm_output_finish, + nullptr, + nullptr, + winmm_output_open, + winmm_output_close, + nullptr, + nullptr, + winmm_output_play, + winmm_output_drain, + winmm_output_cancel, + nullptr, + &winmm_mixer_plugin, +}; diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/WinmmOutputPlugin.hxx new file mode 100644 index 000000000..e8688782e --- /dev/null +++ b/src/output/WinmmOutputPlugin.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_WINMM_OUTPUT_PLUGIN_HXX + +#include "check.h" + +#ifdef ENABLE_WINMM_OUTPUT + +#include "gcc.h" + +#include <windows.h> +#include <mmsystem.h> + +struct WinmmOutput; + +extern const struct audio_output_plugin winmm_output_plugin; + +gcc_pure +HWAVEOUT +winmm_output_get_handle(WinmmOutput *); + +#endif + +#endif diff --git a/src/output/alsa_output_plugin.c b/src/output/alsa_output_plugin.c deleted file mode 100644 index d8b184273..000000000 --- a/src/output/alsa_output_plugin.c +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "alsa_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "pcm_export.h" - -#include <glib.h> -#include <alsa/asoundlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "alsa" - -#define ALSA_PCM_NEW_HW_PARAMS_API -#define ALSA_PCM_NEW_SW_PARAMS_API - -static const char default_device[] = "default"; - -enum { - MPD_ALSA_BUFFER_TIME_US = 500000, -}; - -#define MPD_ALSA_RETRY_NR 5 - -typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, - snd_pcm_uframes_t size); - -struct alsa_data { - struct audio_output base; - - struct pcm_export_state export; - - /** the configured name of the ALSA device; NULL for the - default device */ - char *device; - - /** use memory mapped I/O? */ - bool use_mmap; - - /** - * Enable DSD over USB according to the dCS suggested - * standard? - * - * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf - */ - bool dsd_usb; - - /** libasound's buffer_time setting (in microseconds) */ - unsigned int buffer_time; - - /** libasound's period_time setting (in microseconds) */ - unsigned int period_time; - - /** the mode flags passed to snd_pcm_open */ - int mode; - - /** the libasound PCM device handle */ - snd_pcm_t *pcm; - - /** - * a pointer to the libasound writei() function, which is - * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the - * use_mmap configuration - */ - alsa_writei_t *writei; - - /** - * The size of one audio frame passed to method play(). - */ - size_t in_frame_size; - - /** - * The size of one audio frame passed to libasound. - */ - size_t out_frame_size; - - /** - * The size of one period, in number of frames. - */ - snd_pcm_uframes_t period_frames; - - /** - * The number of frames written in the current period. - */ - snd_pcm_uframes_t period_position; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -alsa_output_quark(void) -{ - return g_quark_from_static_string("alsa_output"); -} - -static const char * -alsa_device(const struct alsa_data *ad) -{ - return ad->device != NULL ? ad->device : default_device; -} - -static struct alsa_data * -alsa_data_new(void) -{ - struct alsa_data *ret = g_new(struct alsa_data, 1); - - ret->mode = 0; - ret->writei = snd_pcm_writei; - - return ret; -} - -static void -alsa_configure(struct alsa_data *ad, const struct config_param *param) -{ - ad->device = config_dup_block_string(param, "device", NULL); - - ad->use_mmap = config_get_block_bool(param, "use_mmap", false); - - ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); - - ad->buffer_time = config_get_block_unsigned(param, "buffer_time", - MPD_ALSA_BUFFER_TIME_US); - ad->period_time = config_get_block_unsigned(param, "period_time", 0); - -#ifdef SND_PCM_NO_AUTO_RESAMPLE - if (!config_get_block_bool(param, "auto_resample", true)) - ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; -#endif - -#ifdef SND_PCM_NO_AUTO_CHANNELS - if (!config_get_block_bool(param, "auto_channels", true)) - ad->mode |= SND_PCM_NO_AUTO_CHANNELS; -#endif - -#ifdef SND_PCM_NO_AUTO_FORMAT - if (!config_get_block_bool(param, "auto_format", true)) - ad->mode |= SND_PCM_NO_AUTO_FORMAT; -#endif -} - -static struct audio_output * -alsa_init(const struct config_param *param, GError **error_r) -{ - struct alsa_data *ad = alsa_data_new(); - - if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { - g_free(ad); - return NULL; - } - - alsa_configure(ad, param); - - return &ad->base; -} - -static void -alsa_finish(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - ao_base_finish(&ad->base); - - g_free(ad->device); - g_free(ad); - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - pcm_export_init(&ad->export); - return true; -} - -static void -alsa_output_disable(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - pcm_export_deinit(&ad->export); -} - -static bool -alsa_test_default_device(void) -{ - snd_pcm_t *handle; - - int ret = snd_pcm_open(&handle, default_device, - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (ret) { - g_message("Error opening default ALSA device: %s\n", - snd_strerror(-ret)); - return false; - } else - snd_pcm_close(handle); - - return true; -} - -static snd_pcm_format_t -get_bitformat(enum sample_format sample_format) -{ - switch (sample_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - return SND_PCM_FORMAT_UNKNOWN; - - case SAMPLE_FORMAT_S8: - 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_S32: - return SND_PCM_FORMAT_S32; - - case SAMPLE_FORMAT_FLOAT: - return SND_PCM_FORMAT_FLOAT; - } - - assert(false); - 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; - } -} - -static snd_pcm_format_t -alsa_to_packed_format(snd_pcm_format_t fmt) -{ - switch (fmt) { - case SND_PCM_FORMAT_S24_LE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_BE: - return SND_PCM_FORMAT_S24_3BE; - - default: - return SND_PCM_FORMAT_UNKNOWN; - } -} - -static int -alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - snd_pcm_format_t fmt, bool *packed_r) -{ - int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = false; - - if (err != -EINVAL) - return err; - - fmt = alsa_to_packed_format(fmt); - if (fmt == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = 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(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - enum sample_format sample_format, - bool *packed_r, bool *reverse_endian_r) -{ - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, - packed_r); - if (err == 0) - *reverse_endian_r = false; - - if (err != -EINVAL) - return err; - - alsa_format = byteswap_bitformat(alsa_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); - if (err == 0) - *reverse_endian_r = true; - - return err; -} - -/** - * 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, - bool *packed_r, bool *reverse_endian_r) -{ - /* try the input format first */ - - int err = alsa_output_try_format(pcm, hwparams, audio_format->format, - packed_r, reverse_endian_r); - - /* if unsupported by the hardware, try other formats */ - - static const enum sample_format probe_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED, - }; - - for (unsigned i = 0; - err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; - ++i) { - const enum sample_format mpd_format = probe_formats[i]; - if (mpd_format == audio_format->format) - continue; - - err = alsa_output_try_format(pcm, hwparams, mpd_format, - packed_r, reverse_endian_r); - if (err == 0) - audio_format->format = mpd_format; - } - - return err; -} - -/** - * Set up the snd_pcm_t object which was opened by the caller. Set up - * the configured settings and the audio format. - */ -static bool -alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - bool *packed_r, bool *reverse_endian_r, GError **error) -{ - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; - unsigned int sample_rate = audio_format->sample_rate; - unsigned int channels = audio_format->channels; - snd_pcm_uframes_t alsa_buffer_size; - snd_pcm_uframes_t alsa_period_size; - int err; - const char *cmd = NULL; - int retry = MPD_ALSA_RETRY_NR; - unsigned int period_time, period_time_ro; - unsigned int buffer_time; - - period_time_ro = period_time = ad->period_time; -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) - goto error; - - if (ad->use_mmap) { - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_MMAP_INTERLEAVED); - if (err < 0) { - g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n", - alsa_device(ad), snd_strerror(-err)); - g_warning("Falling back to direct write mode\n"); - ad->use_mmap = false; - } else - ad->writei = snd_pcm_mmap_writei; - } - - if (!ad->use_mmap) { - cmd = "snd_pcm_hw_params_set_access"; - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED); - if (err < 0) - goto error; - ad->writei = snd_pcm_writei; - } - - err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, - packed_r, reverse_endian_r); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), - sample_format_to_string(audio_format->format), - snd_strerror(-err)); - return false; - } - - snd_pcm_format_t format; - if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) - g_debug("format=%s (%s)", snd_pcm_format_name(format), - snd_pcm_format_description(format)); - - err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, - &channels); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format->channels, - snd_strerror(-err)); - return false; - } - audio_format->channels = (int8_t)channels; - - err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, - &sample_rate, NULL); - if (err < 0 || sample_rate == 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format->sample_rate); - return false; - } - 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"; - err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, - &buffer_time, NULL); - if (err < 0) - goto error; - } else { - err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, - NULL); - if (err < 0) - buffer_time = 0; - } - - if (period_time_ro == 0 && buffer_time >= 10000) { - period_time_ro = period_time = buffer_time / 4; - - g_debug("default period_time = buffer_time/4 = %u/4 = %u", - buffer_time, period_time); - } - - if (period_time_ro > 0) { - period_time = period_time_ro; - cmd = "snd_pcm_hw_params_set_period_time_near"; - err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, - &period_time, NULL); - if (err < 0) - goto error; - } - - cmd = "snd_pcm_hw_params"; - err = snd_pcm_hw_params(ad->pcm, hwparams); - if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { - period_time_ro = period_time_ro >> 1; - goto configure_hw; - } else if (err < 0) - goto error; - if (retry != MPD_ALSA_RETRY_NR) - g_debug("ALSA period_time set to %d\n", period_time); - - cmd = "snd_pcm_hw_params_get_buffer_size"; - err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_hw_params_get_period_size"; - err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, - NULL); - if (err < 0) - goto error; - - /* configure SW params */ - snd_pcm_sw_params_alloca(&swparams); - - cmd = "snd_pcm_sw_params_current"; - err = snd_pcm_sw_params_current(ad->pcm, swparams); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_start_threshold"; - err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, - alsa_buffer_size - - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_avail_min"; - err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params"; - err = snd_pcm_sw_params(ad->pcm, swparams); - if (err < 0) - goto error; - - g_debug("buffer_size=%u period_size=%u", - (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); - - if (alsa_period_size == 0) - /* this works around a SIGFPE bug that occurred when - an ALSA driver indicated period_size==0; this - caused a division by zero in alsa_play(). By using - the fallback "1", we make sure that this won't - happen again. */ - alsa_period_size = 1; - - ad->period_frames = alsa_period_size; - ad->period_position = 0; - - return true; - -error: - g_set_error(error, alsa_output_quark(), err, - "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); - return false; -} - -static bool -alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, - bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - GError **error_r) -{ - assert(ad->dsd_usb); - assert(audio_format->format == SAMPLE_FORMAT_DSD); - - /* pass 24 bit to alsa_setup() */ - - struct audio_format usb_format = *audio_format; - usb_format.format = SAMPLE_FORMAT_S24_P32; - usb_format.sample_rate /= 2; - - const struct audio_format check = usb_format; - - if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) - return false; - - /* if the device allows only 32 bit, shift all DSD-over-USB - samples left by 8 bit and leave the lower 8 bit cleared; - the DSD-over-USB documentation does not specify whether - this is legal, but there is anecdotical evidence that this - is possible (and the only option for some devices) */ - *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; - if (usb_format.format == SAMPLE_FORMAT_S32) - usb_format.format = SAMPLE_FORMAT_S24_P32; - - if (!audio_format_equals(&usb_format, &check)) { - /* no bit-perfect playback, which is required - for DSD over USB */ - g_set_error(error_r, alsa_output_quark(), 0, - "Failed to configure DSD-over-USB on ALSA device \"%s\"", - alsa_device(ad)); - return false; - } - - return true; -} - -static bool -alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, - GError **error_r) -{ - bool shift8 = false, packed, reverse_endian; - - const bool dsd_usb = ad->dsd_usb && - audio_format->format == SAMPLE_FORMAT_DSD; - const bool success = dsd_usb - ? alsa_setup_dsd(ad, audio_format, - &shift8, &packed, &reverse_endian, - error_r) - : alsa_setup(ad, audio_format, &packed, &reverse_endian, - error_r); - if (!success) - return false; - - pcm_export_open(&ad->export, - audio_format->format, audio_format->channels, - dsd_usb, shift8, packed, reverse_endian); - return true; -} - -static bool -alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - int err; - bool success; - - err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "Failed to open ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(err)); - return false; - } - - g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); - - success = alsa_setup_or_dsd(ad, audio_format, error); - if (!success) { - snd_pcm_close(ad->pcm); - return false; - } - - ad->in_frame_size = audio_format_frame_size(audio_format); - ad->out_frame_size = pcm_export_frame_size(&ad->export, audio_format); - - return true; -} - -static int -alsa_recover(struct alsa_data *ad, int err) -{ - if (err == -EPIPE) { - g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); - } else if (err == -ESTRPIPE) { - g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad)); - } - - switch (snd_pcm_state(ad->pcm)) { - case SND_PCM_STATE_PAUSED: - err = snd_pcm_pause(ad->pcm, /* disable */ 0); - break; - case SND_PCM_STATE_SUSPENDED: - err = snd_pcm_resume(ad->pcm); - if (err == -EAGAIN) - return 0; - /* 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: - break; - /* this is no error, so just keep running */ - case SND_PCM_STATE_RUNNING: - err = 0; - break; - default: - /* unknown state, do nothing */ - break; - } - - return err; -} - -static void -alsa_drain(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - 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->out_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(¶ms); - 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(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - ad->period_position = 0; - - snd_pcm_drop(ad->pcm); -} - -static void -alsa_close(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - snd_pcm_close(ad->pcm); -} - -static size_t -alsa_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - assert(size % ad->in_frame_size == 0); - - chunk = pcm_export(&ad->export, chunk, size, &size); - - assert(size % ad->out_frame_size == 0); - - size /= ad->out_frame_size; - - while (true) { - snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) { - ad->period_position = (ad->period_position + ret) - % ad->period_frames; - - size_t bytes_written = ret * ad->out_frame_size; - return pcm_export_source_size(&ad->export, - bytes_written); - } - - if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { - g_set_error(error, alsa_output_quark(), errno, - "%s", snd_strerror(-errno)); - return 0; - } - } -} - -const struct audio_output_plugin alsa_output_plugin = { - .name = "alsa", - .test_default_device = alsa_test_default_device, - .init = alsa_init, - .finish = alsa_finish, - .enable = alsa_output_enable, - .disable = alsa_output_disable, - .open = alsa_open, - .play = alsa_play, - .drain = alsa_drain, - .cancel = alsa_cancel, - .close = alsa_close, - - .mixer_plugin = &alsa_mixer_plugin, -}; diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h deleted file mode 100644 index daa1f3615..000000000 --- a/src/output/alsa_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ALSA_OUTPUT_PLUGIN_H -#define MPD_ALSA_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin alsa_output_plugin; - -#endif diff --git a/src/output/ao_output_plugin.c b/src/output/ao_output_plugin.c deleted file mode 100644 index d7e577fa4..000000000 --- a/src/output/ao_output_plugin.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ao_output_plugin.h" -#include "output_api.h" - -#include <ao/ao.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ao" - -/* An ao_sample_format, with all fields set to zero: */ -static const ao_sample_format OUR_AO_FORMAT_INITIALIZER; - -static unsigned ao_output_ref; - -struct ao_data { - struct audio_output base; - - size_t write_size; - int driver; - ao_option *options; - ao_device *device; -} AoData; - -static inline GQuark -ao_output_quark(void) -{ - return g_quark_from_static_string("ao_output"); -} - -static void -ao_output_error(GError **error_r) -{ - const char *error; - - switch (errno) { - case AO_ENODRIVER: - error = "No such libao driver"; - break; - - case AO_ENOTLIVE: - error = "This driver is not a libao live device"; - break; - - case AO_EBADOPTION: - error = "Invalid libao option"; - break; - - case AO_EOPENDEVICE: - error = "Cannot open the libao device"; - break; - - case AO_EFAIL: - error = "Generic libao failure"; - break; - - default: - error = g_strerror(errno); - } - - g_set_error(error_r, ao_output_quark(), errno, - "%s", error); -} - -static struct audio_output * -ao_output_init(const struct config_param *param, - GError **error) -{ - struct ao_data *ad = g_new(struct ao_data, 1); - - if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) { - g_free(ad); - return NULL; - } - - ao_info *ai; - const char *value; - - ad->options = NULL; - - ad->write_size = config_get_block_unsigned(param, "write_size", 1024); - - if (ao_output_ref == 0) { - ao_initialize(); - } - ao_output_ref++; - - value = config_get_block_string(param, "driver", "default"); - if (0 == strcmp(value, "default")) - ad->driver = ao_default_driver_id(); - else - ad->driver = ao_driver_id(value); - - if (ad->driver < 0) { - g_set_error(error, ao_output_quark(), 0, - "\"%s\" is not a valid ao driver", - value); - ao_base_finish(&ad->base); - g_free(ad); - return NULL; - } - - if ((ai = ao_driver_info(ad->driver)) == NULL) { - g_set_error(error, ao_output_quark(), 0, - "problems getting driver info"); - ao_base_finish(&ad->base); - g_free(ad); - return NULL; - } - - g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name, - config_get_block_string(param, "name", NULL)); - - value = config_get_block_string(param, "options", NULL); - if (value != NULL) { - gchar **options = g_strsplit(value, ";", 0); - - for (unsigned i = 0; options[i] != NULL; ++i) { - gchar **key_value = g_strsplit(options[i], "=", 2); - - if (key_value[0] == NULL || key_value[1] == NULL) { - g_set_error(error, ao_output_quark(), 0, - "problems parsing options \"%s\"", - options[i]); - ao_base_finish(&ad->base); - g_free(ad); - return NULL; - } - - ao_append_option(&ad->options, key_value[0], - key_value[1]); - - g_strfreev(key_value); - } - - g_strfreev(options); - } - - return &ad->base; -} - -static void -ao_output_finish(struct audio_output *ao) -{ - struct ao_data *ad = (struct ao_data *)ao; - - ao_free_options(ad->options); - ao_base_finish(&ad->base); - g_free(ad); - - ao_output_ref--; - - if (ao_output_ref == 0) - ao_shutdown(); -} - -static void -ao_output_close(struct audio_output *ao) -{ - struct ao_data *ad = (struct ao_data *)ao; - - ao_close(ad->device); -} - -static bool -ao_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; - struct ao_data *ad = (struct ao_data *)ao; - - 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.rate = audio_format->sample_rate; - format.byte_format = AO_FMT_NATIVE; - format.channels = audio_format->channels; - - ad->device = ao_open_live(ad->driver, &format, ad->options); - - if (ad->device == NULL) { - ao_output_error(error); - return false; - } - - return true; -} - -/** - * For whatever reason, libao wants a non-const pointer. Let's hope - * it does not write to the buffer, and use the union deconst hack to - * work around this API misdesign. - */ -static int ao_play_deconst(ao_device *device, const void *output_samples, - uint_32 num_bytes) -{ - union { - const void *in; - void *out; - } u; - - u.in = output_samples; - return ao_play(device, u.out, num_bytes); -} - -static size_t -ao_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct ao_data *ad = (struct ao_data *)ao; - - if (size > ad->write_size) - size = ad->write_size; - - if (ao_play_deconst(ad->device, chunk, size) == 0) { - ao_output_error(error); - return 0; - } - - return size; -} - -const struct audio_output_plugin ao_output_plugin = { - .name = "ao", - .init = ao_output_init, - .finish = ao_output_finish, - .open = ao_output_open, - .close = ao_output_close, - .play = ao_output_play, -}; diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h deleted file mode 100644 index 9a3a47c05..000000000 --- a/src/output/ao_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AO_OUTPUT_PLUGIN_H -#define MPD_AO_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin ao_output_plugin; - -#endif diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c deleted file mode 100644 index ba239a4ad..000000000 --- a/src/output/ffado_output_plugin.c +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * 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 "ffado_output_plugin.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 { - struct audio_output base; - - 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 struct audio_output * -ffado_init(const struct config_param *param, - GError **error_r) -{ - g_debug("using libffado version %s, API=%d", - ffado_get_version(), ffado_get_api_version()); - - struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1); - if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) { - g_free(fd); - return NULL; - } - - fd->device_name = config_dup_block_string(param, "device", NULL); - fd->verbose = config_get_block_unsigned(param, "verbose", 0); - - fd->period_size = config_get_block_unsigned(param, "period_size", - 1024); - if (fd->period_size == 0 || fd->period_size > 1024 * 1024) { - ao_base_finish(&fd->base); - g_set_error(error_r, ffado_output_quark(), 0, - "invalid period_size setting"); - return false; - } - - fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3); - if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) { - ao_base_finish(&fd->base); - g_set_error(error_r, ffado_output_quark(), 0, - "invalid nb_buffers setting"); - return false; - } - - return &fd->base; -} - -static void -ffado_finish(struct audio_output *ao) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - g_free(fd->device_name); - ao_base_finish(&fd->base); - 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(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - /* 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(struct audio_output *ao) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - 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(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - /* 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/ffado_output_plugin.h b/src/output/ffado_output_plugin.h deleted file mode 100644 index 4dde01859..000000000 --- a/src/output/ffado_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FFADO_OUTPUT_PLUGIN_H -#define MPD_FFADO_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin ffado_output_plugin; - -#endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c deleted file mode 100644 index 022be0b4a..000000000 --- a/src/output/fifo_output_plugin.c +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "fifo_output_plugin.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 <errno.h> -#include <string.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "fifo" - -#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ - -struct fifo_data { - struct audio_output base; - - char *path; - int input; - int output; - bool created; - struct timer *timer; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -fifo_output_quark(void) -{ - return g_quark_from_static_string("fifo_output"); -} - -static struct fifo_data *fifo_data_new(void) -{ - struct fifo_data *ret; - - ret = g_new(struct fifo_data, 1); - - ret->path = NULL; - ret->input = -1; - ret->output = -1; - ret->created = false; - - return ret; -} - -static void fifo_data_free(struct fifo_data *fd) -{ - g_free(fd->path); - g_free(fd); -} - -static void fifo_delete(struct fifo_data *fd) -{ - g_debug("Removing FIFO \"%s\"", fd->path); - - if (unlink(fd->path) < 0) { - g_warning("Could not remove FIFO \"%s\": %s", - fd->path, g_strerror(errno)); - return; - } - - fd->created = false; -} - -static void -fifo_close(struct fifo_data *fd) -{ - struct stat st; - - if (fd->input >= 0) { - close(fd->input); - fd->input = -1; - } - - if (fd->output >= 0) { - close(fd->output); - fd->output = -1; - } - - if (fd->created && (stat(fd->path, &st) == 0)) - fifo_delete(fd); -} - -static bool -fifo_make(struct fifo_data *fd, GError **error) -{ - if (mkfifo(fd->path, 0666) < 0) { - g_set_error(error, fifo_output_quark(), errno, - "Couldn't create FIFO \"%s\": %s", - fd->path, g_strerror(errno)); - return false; - } - - fd->created = true; - - return true; -} - -static bool -fifo_check(struct fifo_data *fd, GError **error) -{ - struct stat st; - - if (stat(fd->path, &st) < 0) { - if (errno == ENOENT) { - /* Path doesn't exist */ - return fifo_make(fd, error); - } - - g_set_error(error, fifo_output_quark(), errno, - "Failed to stat FIFO \"%s\": %s", - fd->path, g_strerror(errno)); - return false; - } - - if (!S_ISFIFO(st.st_mode)) { - g_set_error(error, fifo_output_quark(), 0, - "\"%s\" already exists, but is not a FIFO", - fd->path); - return false; - } - - return true; -} - -static bool -fifo_open(struct fifo_data *fd, GError **error) -{ - if (!fifo_check(fd, error)) - return false; - - 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", - fd->path, g_strerror(errno)); - fifo_close(fd); - return false; - } - - 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", - fd->path, g_strerror(errno)); - fifo_close(fd); - return false; - } - - return true; -} - -static struct audio_output * -fifo_output_init(const struct config_param *param, - GError **error_r) -{ - struct fifo_data *fd; - - GError *error = NULL; - char *path = config_dup_block_path(param, "path", &error); - if (!path) { - if (error != NULL) - g_propagate_error(error_r, error); - else - g_set_error(error_r, fifo_output_quark(), 0, - "No \"path\" parameter specified"); - return NULL; - } - - fd = fifo_data_new(); - fd->path = path; - - if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) { - fifo_data_free(fd); - return NULL; - } - - if (!fifo_open(fd, error_r)) { - ao_base_finish(&fd->base); - fifo_data_free(fd); - return NULL; - } - - return &fd->base; -} - -static void -fifo_output_finish(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - fifo_close(fd); - ao_base_finish(&fd->base); - fifo_data_free(fd); -} - -static bool -fifo_output_open(struct audio_output *ao, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - fd->timer = timer_new(audio_format); - - return true; -} - -static void -fifo_output_close(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - timer_free(fd->timer); -} - -static void -fifo_output_cancel(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - char buf[FIFO_BUFFER_SIZE]; - int bytes = 1; - - timer_reset(fd->timer); - - while (bytes > 0 && errno != EINTR) - bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); - - if (bytes < 0 && errno != EAGAIN) { - g_warning("Flush of FIFO \"%s\" failed: %s", - fd->path, g_strerror(errno)); - } -} - -static unsigned -fifo_output_delay(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - return fd->timer->started - ? timer_delay(fd->timer) - : 0; -} - -static size_t -fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - ssize_t bytes; - - if (!fd->timer->started) - timer_start(fd->timer); - timer_add(fd->timer, size); - - while (true) { - bytes = write(fd->output, chunk, size); - if (bytes > 0) - return (size_t)bytes; - - if (bytes < 0) { - switch (errno) { - case EAGAIN: - /* The pipe is full, so empty it */ - fifo_output_cancel(&fd->base); - continue; - case EINTR: - continue; - } - - g_set_error(error, fifo_output_quark(), errno, - "Failed to write to FIFO %s: %s", - fd->path, g_strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin fifo_output_plugin = { - .name = "fifo", - .init = fifo_output_init, - .finish = fifo_output_finish, - .open = fifo_output_open, - .close = fifo_output_close, - .delay = fifo_output_delay, - .play = fifo_output_play, - .cancel = fifo_output_cancel, -}; diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h deleted file mode 100644 index 85f7985e1..000000000 --- a/src/output/fifo_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FIFO_OUTPUT_PLUGIN_H -#define MPD_FIFO_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin fifo_output_plugin; - -#endif diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c deleted file mode 100644 index 72de90457..000000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_client.h" -#include "httpd_internal.h" -#include "fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - 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)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* 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 */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index 739163f42..000000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h deleted file mode 100644 index 5dcb8ab9b..000000000 --- a/src/output/httpd_internal.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal declarations for the "httpd" audio output plugin. - */ - -#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H -#define MPD_OUTPUT_HTTPD_INTERNAL_H - -#include "output_internal.h" -#include "timer.h" - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; - -struct httpd_output { - struct audio_output base; - - /** - * True if the audio output is open and accepts client - * connections. - */ - bool open; - - /** - * The configured encoder plugin. - */ - struct encoder *encoder; - - /** - * Number of bytes which were fed into the encoder, without - * ever receiving new output. This is used to estimate - * whether MPD should manually flush the encoder, to avoid - * buffer underruns in the client. - */ - size_t unflushed_input; - - /** - * The MIME type produced by the #encoder. - */ - const char *content_type; - - /** - * This mutex protects the listener socket and the client - * list. - */ - GMutex *mutex; - - /** - * A #timer object to synchronize this output with the - * wallclock. - */ - struct timer *timer; - - /** - * The listener socket. - */ - struct server_socket *server_socket; - - /** - * The header page, which is sent to every client on connect. - */ - struct page *header; - - /** - * The metadata, which is sent to every client. - */ - 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. - */ - GList *clients; - - /** - * A temporary buffer for the httpd_output_read_page() - * function. - */ - char buffer[32768]; - - /** - * The maximum and current number of clients connected - * at the same time. - */ - guint clients_max, clients_cnt; -}; - -/** - * Removes a client from the httpd_output.clients linked list. - */ -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); - -/** - * Sends the encoder header to the client. This is called right after - * the response headers have been sent. - */ -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); - -#endif diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c deleted file mode 100644 index 1d730df7f..000000000 --- a/src/output/httpd_output_plugin.c +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "resolver.h" -#include "page.h" -#include "icy_server.h" -#include "fd_util.h" -#include "server_socket.h" - -#include <assert.h> - -#include <sys/types.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" - -/** - * The quark used for GError.domain. - */ -static inline GQuark -httpd_output_quark(void) -{ - return g_quark_from_static_string("httpd_output"); -} - -/** - * Check whether there is at least one client. - * - * Caller must lock the mutex. - */ -G_GNUC_PURE -static bool -httpd_output_has_clients(const struct httpd_output *httpd) -{ - return httpd->clients != NULL; -} - -/** - * Check whether there is at least one client. - */ -G_GNUC_PURE -static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) -{ - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; -} - -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 struct audio_output * -httpd_output_init(const struct config_param *param, - GError **error) -{ - struct httpd_output *httpd = g_new(struct httpd_output, 1); - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { - g_free(httpd); - return NULL; - } - - /* 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"); - - guint port = config_get_block_unsigned(param, "port", 8000); - - const char *encoder_name = - config_get_block_string(param, "encoder", "vorbis"); - const struct encoder_plugin *encoder_plugin = - encoder_plugin_get(encoder_name); - if (encoder_plugin == NULL) { - g_set_error(error, httpd_output_quark(), 0, - "No such encoder: %s", encoder_name); - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); - - /* set up bind_to_address */ - - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - - const char *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) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* initialize metadata */ - httpd->metadata = NULL; - httpd->unflushed_input = 0; - - /* initialize encoder */ - - httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* determine content type */ - httpd->content_type = encoder_get_mime_type(httpd->encoder); - if (httpd->content_type == NULL) { - httpd->content_type = "application/octet-stream"; - } - - httpd->mutex = g_mutex_new(); - - return &httpd->base; -} - -static void -httpd_output_finish(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd->metadata) - page_unref(httpd->metadata); - - encoder_finish(httpd->encoder); - server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); - ao_base_finish(&httpd->base); - g_free(httpd); -} - -/** - * Creates a new #httpd_client object and adds it into the - * httpd_output.clients linked list. - */ -static void -httpd_client_add(struct httpd_output *httpd, int fd) -{ - struct httpd_client *client = - httpd_client_new(httpd, 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 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 = ctx; - - /* the listener socket has become readable - a client has - connected */ - -#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_socket(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_socket(fd); - } else if (fd < 0 && errno != EINTR) { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - g_mutex_unlock(httpd->mutex); -} - -/** - * Reads data from the encoder (as much as available) and returns it - * as a new #page object. - */ -static struct page * -httpd_output_read_page(struct httpd_output *httpd) -{ - 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; - } - - size_t size = 0; - do { - size_t 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)); - - if (size == 0) - return NULL; - - return page_new_copy(httpd->buffer, size); -} - -static bool -httpd_output_encoder_open(struct httpd_output *httpd, - struct audio_format *audio_format, - GError **error) -{ - if (!encoder_open(httpd->encoder, audio_format, error)) - return false; - - /* we have to remember the encoder header, i.e. the first - 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(struct audio_output *ao, GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - return httpd_output_bind(httpd, error_r); -} - -static void -httpd_output_disable(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - httpd_output_unbind(httpd); -} - -static bool -httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - /* open the encoder */ - - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); - return false; - } - - /* 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; -} - -static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void -httpd_output_close(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - httpd->open = false; - - timer_free(httpd->timer); - - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); - - if (httpd->header != NULL) - page_unref(httpd->header); - - encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); -} - -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) -{ - assert(httpd != NULL); - assert(client != NULL); - - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; -} - -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) -{ - if (httpd->header != NULL) - httpd_client_send(client, httpd->header); -} - -static unsigned -httpd_output_delay(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { - /* if there's no client and this output is paused, - then httpd_output_pause() will not do anything, it - will not fill the buffer and it will not update the - timer; therefore, we reset the timer here */ - timer_reset(httpd->timer); - - /* some arbitrary delay that is long enough to avoid - consuming too much CPU, and short enough to notice - new clients quickly enough */ - return 1000; - } - - return httpd->timer->started - ? timer_delay(httpd->timer) - : 0; -} - -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - -/** - * Broadcasts a page struct to all clients. - */ -static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) -{ - assert(page != NULL); - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); -} - -/** - * Broadcasts data from the encoder to all clients. - */ -static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) -{ - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); - - while ((page = httpd_output_read_page(httpd)) != NULL) { - httpd_output_broadcast_page(httpd, page); - page_unref(page); - } -} - -static bool -httpd_output_encode_and_play(struct httpd_output *httpd, - const void *chunk, size_t size, GError **error) -{ - if (!encoder_write(httpd->encoder, chunk, size, error)) - return false; - - httpd->unflushed_input += size; - - httpd_output_encoder_to_clients(httpd); - - return true; -} - -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) - return 0; - } - - if (!httpd->timer->started) - timer_start(httpd->timer); - timer_add(httpd->timer, size); - - return size; -} - -static bool -httpd_output_pause(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; - return httpd_output_play(ao, silence, sizeof(silence), - NULL) > 0; - } else { - return true; - } -} - -static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void -httpd_output_tag(struct audio_output *ao, const struct tag *tag) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - assert(tag != NULL); - - if (httpd->encoder->plugin->tag != NULL) { - /* embed encoder tags */ - - /* flush the current stream, and end it */ - - encoder_pre_tag(httpd->encoder, NULL); - httpd_output_encoder_to_clients(httpd); - - /* send the tag to the encoder - which starts a new - stream now */ - - encoder_tag(httpd->encoder, tag, NULL); - - /* the first page generated by the encoder will now be - used as the new "header" page, which is sent to all - new clients */ - - struct page *page = httpd_output_read_page(httpd); - if (page != NULL) { - if (httpd->header != NULL) - page_unref(httpd->header); - httpd->header = page; - httpd_output_broadcast_page(httpd, page); - } - } else { - /* use Icy-Metadata */ - - if (httpd->metadata != NULL) - page_unref (httpd->metadata); - - httpd->metadata = - 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); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); - } - } -} - -static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void -httpd_output_cancel(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); -} - -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/httpd_output_plugin.h b/src/output/httpd_output_plugin.h deleted file mode 100644 index d0eb1533f..000000000 --- a/src/output/httpd_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin httpd_output_plugin; - -#endif diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c deleted file mode 100644 index d5c8ca412..000000000 --- a/src/output/jack_output_plugin.c +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "jack_output_plugin.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 { - struct audio_output base; - - /** - * 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); - if (out == NULL) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - 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); - if (out == NULL) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - 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 struct audio_output * -mpd_jack_init(const struct config_param *param, GError **error_r) -{ - struct jack_data *jd = g_new(struct jack_data, 1); - - if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) { - g_free(jd); - return NULL; - } - - const char *value; - - jd->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->base; -} - -static void -mpd_jack_finish(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - 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]); - - ao_base_finish(&jd->base); - g_free(jd); -} - -static bool -mpd_jack_enable(struct audio_output *ao, GError **error_r) -{ - struct jack_data *jd = (struct jack_data *)ao; - - 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(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - 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(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) -{ - struct jack_data *jd = (struct jack_data *)ao; - - assert(jd != NULL); - - jd->pause = false; - - if (jd->client != NULL && jd->shutdown) - mpd_jack_disconnect(jd); - - 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 struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - mpd_jack_stop(jd); -} - -static unsigned -mpd_jack_delay(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - return jd->base.pause && jd->pause && !jd->shutdown - ? 1000 - : 0; -} - -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(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct jack_data *jd = (struct jack_data *)ao; - const size_t frame_size = audio_format_frame_size(&jd->audio_format); - size_t space = 0, space1; - - 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(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - if (jd->shutdown) - return false; - - jd->pause = true; - - 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, - .delay = mpd_jack_delay, - .play = mpd_jack_play, - .pause = mpd_jack_pause, - .close = mpd_jack_close, -}; diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h deleted file mode 100644 index 2f94ae7dc..000000000 --- a/src/output/jack_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_JACK_OUTPUT_PLUGIN_H -#define MPD_JACK_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin jack_output_plugin; - -#endif diff --git a/src/output/mvp_output_plugin.c b/src/output/mvp_output_plugin.c deleted file mode 100644 index 37e0f7c93..000000000 --- a/src/output/mvp_output_plugin.c +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Media MVP audio output based on code from MVPMC project: - * http://mvpmc.sourceforge.net/ - */ - -#include "config.h" -#include "mvp_output_plugin.h" -#include "output_api.h" -#include "fd_util.h" - -#include <glib.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mvp" - -typedef struct { - unsigned long dsp_status; - unsigned long stream_decode_type; - unsigned long sample_rate; - unsigned long bit_rate; - unsigned long raw[64 / sizeof(unsigned long)]; -} aud_status_t; - -#define MVP_SET_AUD_STOP _IOW('a',1,int) -#define MVP_SET_AUD_PLAY _IOW('a',2,int) -#define MVP_SET_AUD_PAUSE _IOW('a',3,int) -#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int) -#define MVP_SET_AUD_SRC _IOW('a',5,int) -#define MVP_SET_AUD_MUTE _IOW('a',6,int) -#define MVP_SET_AUD_BYPASS _IOW('a',8,int) -#define MVP_SET_AUD_CHANNEL _IOW('a',9,int) -#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t) -#define MVP_SET_AUD_VOLUME _IOW('a',13,int) -#define MVP_GET_AUD_VOLUME _IOR('a',14,int) -#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int) -#define MVP_SET_AUD_FORMAT _IOW('a',16,int) -#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*) -#define MVP_SET_AUD_STC _IOW('a',22,long long int *) -#define MVP_SET_AUD_SYNC _IOW('a',23,int) -#define MVP_SET_AUD_END_STREAM _IOW('a',25,int) -#define MVP_SET_AUD_RESET _IOW('a',26,int) -#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int) -#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) - -struct mvp_data { - struct audio_output base; - - struct audio_format audio_format; - int fd; -}; - -static const unsigned mvp_sample_rates[][3] = { - {9, 8000, 32000}, - {10, 11025, 44100}, - {11, 12000, 48000}, - {1, 16000, 32000}, - {2, 22050, 44100}, - {3, 24000, 48000}, - {5, 32000, 32000}, - {0, 44100, 44100}, - {7, 48000, 48000}, - {13, 64000, 32000}, - {14, 88200, 44100}, - {15, 96000, 48000} -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -mvp_output_quark(void) -{ - return g_quark_from_static_string("mvp_output"); -} - -/** - * Translate a sample rate to a MVP sample rate. - * - * @param sample_rate the sample rate in Hz - */ -static unsigned -mvp_find_sample_rate(unsigned sample_rate) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(mvp_sample_rates); ++i) - if (mvp_sample_rates[i][1] == sample_rate) - return mvp_sample_rates[i][0]; - - return (unsigned)-1; -} - -static bool -mvp_output_test_default_device(void) -{ - int fd; - - fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - - g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n", - g_strerror(errno)); - - return false; -} - -static struct audio_output * -mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error) -{ - struct mvp_data *md = g_new(struct mvp_data, 1); - - if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) { - g_free(md); - return NULL; - } - - md->fd = -1; - - return &md->base; -} - -static void -mvp_output_finish(struct audio_output *ao) -{ - struct mvp_data *md = (struct mvp_data *)ao; - ao_base_finish(&md->base); - g_free(md); -} - -static bool -mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, - GError **error) -{ - unsigned mix[5]; - - switch (audio_format->channels) { - case 1: - mix[0] = 1; - break; - - case 2: - mix[0] = 0; - break; - - default: - g_debug("unsupported channel count %u - falling back to stereo", - audio_format->channels); - audio_format->channels = 2; - mix[0] = 0; - break; - } - - /* 0,1=24bit(24) , 2,3=16bit */ - switch (audio_format->format) { - case SAMPLE_FORMAT_S16: - mix[1] = 2; - break; - - case SAMPLE_FORMAT_S24_P32: - mix[1] = 0; - break; - - default: - 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; - } - - mix[3] = 0; /* stream type? */ - mix[4] = G_BYTE_ORDER == G_LITTLE_ENDIAN; - - /* - * if there is an exact match for the frequency, use it. - */ - mix[2] = mvp_find_sample_rate(audio_format->sample_rate); - if (mix[2] == (unsigned)-1) { - g_set_error(error, mvp_output_quark(), 0, - "Can not find suitable output frequency for %u", - audio_format->sample_rate); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio format"); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio sync"); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio play mode"); - return false; - } - - return true; -} - -static bool -mvp_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct mvp_data *md = (struct mvp_data *)ao; - long long int stc = 0; - int mix[5] = { 0, 2, 7, 1, 0 }; - bool success; - - 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", - g_strerror(errno)); - return false; - } - if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio source: %s", - g_strerror(errno)); - return false; - } - if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio streamtype: %s", - g_strerror(errno)); - return false; - } - if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio format: %s", - g_strerror(errno)); - return false; - } - ioctl(md->fd, MVP_SET_AUD_STC, &stc); - if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio streamtype: %s", - g_strerror(errno)); - return false; - } - - success = mvp_set_pcm_params(md, audio_format, error); - if (!success) - return false; - - md->audio_format = *audio_format; - return true; -} - -static void mvp_output_close(struct audio_output *ao) -{ - struct mvp_data *md = (struct mvp_data *)ao; - if (md->fd >= 0) - close(md->fd); - md->fd = -1; -} - -static void mvp_output_cancel(struct audio_output *ao) -{ - struct mvp_data *md = (struct mvp_data *)ao; - if (md->fd >= 0) { - ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); - close(md->fd); - md->fd = -1; - } -} - -static size_t -mvp_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct mvp_data *md = (struct mvp_data *)ao; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (md->fd < 0) { - bool success; - - success = mvp_output_open(ao, &md->audio_format, error); - if (!success) - return 0; - } - - while (true) { - ret = write(md->fd, chunk, size); - if (ret > 0) - return (size_t)ret; - - if (ret < 0) { - if (errno == EINTR) - continue; - - g_set_error(error, mvp_output_quark(), errno, - "Failed to write: %s", g_strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin mvp_output_plugin = { - .name = "mvp", - .test_default_device = mvp_output_test_default_device, - .init = mvp_output_init, - .finish = mvp_output_finish, - .open = mvp_output_open, - .close = mvp_output_close, - .play = mvp_output_play, - .cancel = mvp_output_cancel, -}; diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h deleted file mode 100644 index e403de2b7..000000000 --- a/src/output/mvp_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MVP_OUTPUT_PLUGIN_H -#define MPD_MVP_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin mvp_output_plugin; - -#endif diff --git a/src/output/null_output_plugin.c b/src/output/null_output_plugin.c deleted file mode 100644 index 9d7588fff..000000000 --- a/src/output/null_output_plugin.c +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "null_output_plugin.h" -#include "output_api.h" -#include "timer.h" - -#include <glib.h> - -#include <assert.h> - -struct null_data { - struct audio_output base; - - bool sync; - - struct timer *timer; -}; - -static struct audio_output * -null_init(const struct config_param *param, GError **error_r) -{ - struct null_data *nd = g_new(struct null_data, 1); - - if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { - g_free(nd); - return NULL; - } - - nd->sync = config_get_block_bool(param, "sync", true); - - return &nd->base; -} - -static void -null_finish(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - ao_base_finish(&nd->base); - g_free(nd); -} - -static bool -null_open(struct audio_output *ao, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct null_data *nd = (struct null_data *)ao; - - if (nd->sync) - nd->timer = timer_new(audio_format); - - return true; -} - -static void -null_close(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - if (nd->sync) - timer_free(nd->timer); -} - -static unsigned -null_delay(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - return nd->sync && nd->timer->started - ? timer_delay(nd->timer) - : 0; -} - -static size_t -null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct null_data *nd = (struct null_data *)ao; - struct timer *timer = nd->timer; - - if (!nd->sync) - return size; - - if (!timer->started) - timer_start(timer); - timer_add(timer, size); - - return size; -} - -static void -null_cancel(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - if (!nd->sync) - return; - - timer_reset(nd->timer); -} - -const struct audio_output_plugin null_output_plugin = { - .name = "null", - .init = null_init, - .finish = null_finish, - .open = null_open, - .close = null_close, - .delay = null_delay, - .play = null_play, - .cancel = null_cancel, -}; diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h deleted file mode 100644 index 392bf0aa3..000000000 --- a/src/output/null_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_NULL_OUTPUT_PLUGIN_H -#define MPD_NULL_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin null_output_plugin; - -#endif diff --git a/src/output/openal_output_plugin.c b/src/output/openal_output_plugin.c deleted file mode 100644 index ebd35ef12..000000000 --- a/src/output/openal_output_plugin.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "openal_output_plugin.h" -#include "output_api.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 { - struct audio_output base; - - const char *device_name; - ALCdevice *device; - ALCcontext *context; - ALuint buffers[NUM_BUFFERS]; - unsigned 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) -{ - /* note: cannot map SAMPLE_FORMAT_S8 to AL_FORMAT_STEREO8 or - AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit - samples, while MPD uses signed samples */ - - 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; - - /* fall back to mono */ - audio_format->channels = 1; - return openal_audio_format(audio_format); - - default: - /* fall back to 16 bit */ - audio_format->format = SAMPLE_FORMAT_S16; - return openal_audio_format(audio_format); - } -} - -G_GNUC_PURE -static inline ALint -openal_get_source_i(const struct openal_data *od, ALenum param) -{ - ALint value; - alGetSourcei(od->source, param, &value); - return value; -} - -G_GNUC_PURE -static inline bool -openal_has_processed(const struct openal_data *od) -{ - return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; -} - -G_GNUC_PURE -static inline ALint -openal_is_playing(const struct openal_data *od) -{ - return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; -} - -static bool -openal_setup_context(struct openal_data *od, - GError **error) -{ - 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 struct audio_output * -openal_init(const struct config_param *param, GError **error_r) -{ - const char *device_name = config_get_block_string(param, "device", NULL); - struct openal_data *od; - - if (device_name == NULL) { - device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); - } - - od = g_new(struct openal_data, 1); - if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) { - g_free(od); - return NULL; - } - - od->device_name = device_name; - - return &od->base; -} - -static void -openal_finish(struct audio_output *ao) -{ - struct openal_data *od = (struct openal_data *)ao; - - ao_base_finish(&od->base); - g_free(od); -} - -static bool -openal_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct openal_data *od = (struct openal_data *)ao; - - od->format = openal_audio_format(audio_format); - - 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->frequency = audio_format->sample_rate; - - return true; -} - -static void -openal_close(struct audio_output *ao) -{ - struct openal_data *od = (struct openal_data *)ao; - - alcMakeContextCurrent(od->context); - alDeleteSources(1, &od->source); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - alcDestroyContext(od->context); - alcCloseDevice(od->device); -} - -static unsigned -openal_delay(struct audio_output *ao) -{ - struct openal_data *od = (struct openal_data *)ao; - - return od->filled < NUM_BUFFERS || openal_has_processed(od) - ? 0 - /* we don't know exactly how long we must wait for the - next buffer to finish, so this is a random - guess: */ - : 50; -} - -static size_t -openal_play(struct audio_output *ao, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct openal_data *od = (struct openal_data *)ao; - ALuint buffer; - - if (alcGetCurrentContext() != od->context) { - alcMakeContextCurrent(od->context); - } - - if (od->filled < NUM_BUFFERS) { - /* fill all buffers */ - buffer = od->buffers[od->filled]; - od->filled++; - } else { - /* wait for processed buffer */ - while (!openal_has_processed(od)) - g_usleep(10); - - alSourceUnqueueBuffers(od->source, 1, &buffer); - } - - alBufferData(buffer, od->format, chunk, size, od->frequency); - alSourceQueueBuffers(od->source, 1, &buffer); - - if (!openal_is_playing(od)) - alSourcePlay(od->source); - - return size; -} - -static void -openal_cancel(struct audio_output *ao) -{ - struct openal_data *od = (struct openal_data *)ao; - - od->filled = 0; - alcMakeContextCurrent(od->context); - alSourceStop(od->source); - - /* force-unqueue all buffers */ - alSourcei(od->source, AL_BUFFER, 0); - od->filled = 0; -} - -const struct audio_output_plugin openal_output_plugin = { - .name = "openal", - .init = openal_init, - .finish = openal_finish, - .open = openal_open, - .close = openal_close, - .delay = openal_delay, - .play = openal_play, - .cancel = openal_cancel, -}; diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h deleted file mode 100644 index 25f6ccf46..000000000 --- a/src/output/openal_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OPENAL_OUTPUT_PLUGIN_H -#define MPD_OPENAL_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin openal_output_plugin; - -#endif diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c deleted file mode 100644 index e366a4537..000000000 --- a/src/output/oss_output_plugin.c +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "oss_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "fd_util.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "oss" - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -/* We got bug reports from FreeBSD users who said that the two 24 bit - formats generate white noise on FreeBSD, but 32 bit works. This is - a workaround until we know what exactly is expected by the kernel - audio drivers. */ -#ifndef __linux__ -#undef AFMT_S24_PACKED -#undef AFMT_S24_NE -#endif - -#ifdef AFMT_S24_PACKED -#include "pcm_export.h" -#endif - -struct oss_data { - struct audio_output base; - -#ifdef AFMT_S24_PACKED - struct pcm_export_state export; -#endif - - int fd; - const char *device; - - /** - * The current input audio format. This is needed to reopen - * the device after cancel(). - */ - struct audio_format audio_format; - - /** - * The current OSS audio format. This is needed to reopen the - * device after cancel(). - */ - int oss_format; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -oss_output_quark(void) -{ - return g_quark_from_static_string("oss_output"); -} - -static struct oss_data * -oss_data_new(void) -{ - struct oss_data *ret = g_new(struct oss_data, 1); - - ret->device = NULL; - ret->fd = -1; - - return ret; -} - -static void -oss_data_free(struct oss_data *od) -{ - g_free(od); -} - -enum oss_stat { - OSS_STAT_NO_ERROR = 0, - OSS_STAT_NOT_CHAR_DEV = -1, - OSS_STAT_NO_PERMS = -2, - OSS_STAT_DOESN_T_EXIST = -3, - OSS_STAT_OTHER = -4, -}; - -static enum oss_stat -oss_stat_device(const char *device, int *errno_r) -{ - struct stat st; - - if (0 == stat(device, &st)) { - if (!S_ISCHR(st.st_mode)) { - return OSS_STAT_NOT_CHAR_DEV; - } - } else { - *errno_r = errno; - - switch (errno) { - case ENOENT: - case ENOTDIR: - return OSS_STAT_DOESN_T_EXIST; - case EACCES: - return OSS_STAT_NO_PERMS; - default: - return OSS_STAT_OTHER; - } - } - - return OSS_STAT_NO_ERROR; -} - -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; - -static bool -oss_output_test_default_device(void) -{ - int fd, i; - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - fd = open_cloexec(default_devices[i], O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - g_warning("Error opening OSS device \"%s\": %s\n", - default_devices[i], g_strerror(errno)); - } - - return false; -} - -static struct audio_output * -oss_open_default(GError **error) -{ - int i; - int err[G_N_ELEMENTS(default_devices)]; - enum oss_stat ret[G_N_ELEMENTS(default_devices)]; - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - ret[i] = oss_stat_device(default_devices[i], &err[i]); - if (ret[i] == OSS_STAT_NO_ERROR) { - struct oss_data *od = oss_data_new(); - if (!ao_base_init(&od->base, &oss_output_plugin, NULL, - error)) { - g_free(od); - return NULL; - } - - od->device = default_devices[i]; - return &od->base; - } - } - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - const char *dev = default_devices[i]; - switch(ret[i]) { - case OSS_STAT_NO_ERROR: - /* never reached */ - break; - case OSS_STAT_DOESN_T_EXIST: - g_warning("%s not found\n", dev); - break; - case OSS_STAT_NOT_CHAR_DEV: - g_warning("%s is not a character device\n", dev); - break; - case OSS_STAT_NO_PERMS: - g_warning("%s: permission denied\n", dev); - break; - case OSS_STAT_OTHER: - g_warning("Error accessing %s: %s\n", - dev, g_strerror(err[i])); - } - } - - g_set_error(error, oss_output_quark(), 0, - "error trying to open default OSS device"); - return NULL; -} - -static struct audio_output * -oss_output_init(const struct config_param *param, GError **error) -{ - const char *device = config_get_block_string(param, "device", NULL); - if (device != NULL) { - struct oss_data *od = oss_data_new(); - if (!ao_base_init(&od->base, &oss_output_plugin, param, - error)) { - g_free(od); - return NULL; - } - - od->device = device; - return &od->base; - } - - return oss_open_default(error); -} - -static void -oss_output_finish(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - ao_base_finish(&od->base); - oss_data_free(od); -} - -#ifdef AFMT_S24_PACKED - -static bool -oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) -{ - struct oss_data *od = (struct oss_data *)ao; - - pcm_export_init(&od->export); - return true; -} - -static void -oss_output_disable(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - pcm_export_deinit(&od->export); -} - -#endif - -static void -oss_close(struct oss_data *od) -{ - if (od->fd >= 0) - close(od->fd); - od->fd = -1; -} - -/** - * A tri-state type for oss_try_ioctl(). - */ -enum oss_setup_result { - SUCCESS, - ERROR, - UNSUPPORTED, -}; - -/** - * 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; -} - -/** - * 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) -{ - return oss_try_ioctl_r(fd, request, &value, msg, error_r); -} - -/** - * Set up the channel number, and attempts to find alternatives if the - * specified number is not supported. - */ -static bool -oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) -{ - 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; - } - - 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; - } - } - - 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: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - return AFMT_QUERY; - - case SAMPLE_FORMAT_S8: - return AFMT_S8; - - case SAMPLE_FORMAT_S16: - return AFMT_S16_NE; - - 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_P32; -#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: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -/** - * Probe one sample format. - * - * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on - * error - */ -static enum oss_setup_result -oss_probe_sample_format(int fd, enum sample_format sample_format, - enum sample_format *sample_format_r, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - struct pcm_export_state *export, -#endif - GError **error_r) -{ - int oss_format = sample_format_to_oss(sample_format); - if (oss_format == AFMT_QUERY) - return UNSUPPORTED; - - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error_r); - -#ifdef AFMT_S24_PACKED - if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { - /* if the driver doesn't support padded 24 bit, try - packed 24 bit */ - oss_format = AFMT_S24_PACKED; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error_r); - } -#endif - - if (result != SUCCESS) - return result; - - sample_format = sample_format_from_oss(oss_format); - if (sample_format == SAMPLE_FORMAT_UNDEFINED) - return UNSUPPORTED; - - *sample_format_r = sample_format; - *oss_format_r = oss_format; - -#ifdef AFMT_S24_PACKED - pcm_export_open(export, sample_format, 0, false, false, - oss_format == AFMT_S24_PACKED, - oss_format == AFMT_S24_PACKED && - G_BYTE_ORDER != G_LITTLE_ENDIAN); -#endif - - return SUCCESS; -} - -/** - * Set up the sample format, and attempts to find alternatives if the - * specified format is not supported. - */ -static bool -oss_setup_sample_format(int fd, struct audio_format *audio_format, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - struct pcm_export_state *export, -#endif - GError **error_r) -{ - enum sample_format mpd_format; - enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format->format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - export, -#endif - error_r); - switch (result) { - case SUCCESS: - audio_format->format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - if (result != UNSUPPORTED) - return result == SUCCESS; - - /* the requested sample format is not available - probe for - other formats supported by MPD */ - - static const enum sample_format sample_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_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; - - result = oss_probe_sample_format(fd, mpd_format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - export, -#endif - error_r); - switch (result) { - case SUCCESS: - audio_format->format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error_literal(error_r, oss_output_quark(), EINVAL, - "Failed to set sample format"); - return false; -} - -/** - * Sets up the OSS device which was opened before. - */ -static bool -oss_setup(struct oss_data *od, struct audio_format *audio_format, - GError **error_r) -{ - 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, &od->oss_format, -#ifdef AFMT_S24_PACKED - &od->export, -#endif - error_r); -} - -/** - * 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, g_strerror(errno)); - return false; - } - - 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"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, - od->oss_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; - } - - return true; -} - -static bool -oss_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct oss_data *od = (struct oss_data *)ao; - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - g_set_error(error, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, g_strerror(errno)); - return false; - } - - if (!oss_setup(od, audio_format, error)) { - oss_close(od); - return false; - } - - od->audio_format = *audio_format; - return true; -} - -static void -oss_output_close(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - oss_close(od); -} - -static void -oss_output_cancel(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - if (od->fd >= 0) { - ioctl(od->fd, SNDCTL_DSP_RESET, 0); - oss_close(od); - } -} - -static size_t -oss_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct oss_data *od = (struct oss_data *)ao; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_reopen(od, error)) - return 0; - -#ifdef AFMT_S24_PACKED - chunk = pcm_export(&od->export, chunk, size, &size); -#endif - - while (true) { - ret = write(od->fd, chunk, size); - if (ret > 0) { -#ifdef AFMT_S24_PACKED - ret = pcm_export_source_size(&od->export, ret); -#endif - return ret; - } - - if (ret < 0 && errno != EINTR) { - g_set_error(error, oss_output_quark(), errno, - "Write error on %s: %s", - od->device, g_strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin oss_output_plugin = { - .name = "oss", - .test_default_device = oss_output_test_default_device, - .init = oss_output_init, - .finish = oss_output_finish, -#ifdef AFMT_S24_PACKED - .enable = oss_output_enable, - .disable = oss_output_disable, -#endif - .open = oss_output_open, - .close = oss_output_close, - .play = oss_output_play, - .cancel = oss_output_cancel, - - .mixer_plugin = &oss_mixer_plugin, -}; diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h deleted file mode 100644 index 2aecc2b3a..000000000 --- a/src/output/oss_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OSS_OUTPUT_PLUGIN_H -#define MPD_OSS_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin oss_output_plugin; - -#endif diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c deleted file mode 100644 index cd51fca20..000000000 --- a/src/output/osx_output_plugin.c +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "osx_output_plugin.h" -#include "output_api.h" -#include "fifo_buffer.h" - -#include <glib.h> -#include <CoreAudio/AudioHardware.h> -#include <AudioUnit/AudioUnit.h> -#include <CoreServices/CoreServices.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "osx" - -struct osx_output { - struct audio_output base; - - /* configuration settings */ - OSType component_subtype; - /* only applicable with kAudioUnitSubType_HALOutput */ - const char *device_name; - - AudioUnit au; - GMutex *mutex; - GCond *condition; - - struct fifo_buffer *buffer; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -osx_output_quark(void) -{ - return g_quark_from_static_string("osx_output"); -} - -static bool -osx_output_test_default_device(void) -{ - /* on a Mac, this is always the default plugin, if nothing - else is configured */ - return true; -} - -static void -osx_output_configure(struct osx_output *oo, const struct config_param *param) -{ - const char *device = config_get_block_string(param, "device", NULL); - - if (device == NULL || 0 == strcmp(device, "default")) { - oo->component_subtype = kAudioUnitSubType_DefaultOutput; - oo->device_name = NULL; - } - else if (0 == strcmp(device, "system")) { - oo->component_subtype = kAudioUnitSubType_SystemOutput; - oo->device_name = NULL; - } - else { - oo->component_subtype = kAudioUnitSubType_HALOutput; - /* XXX am I supposed to g_strdup() this? */ - oo->device_name = device; - } -} - -static struct audio_output * -osx_output_init(const struct config_param *param, GError **error_r) -{ - struct osx_output *oo = g_new(struct osx_output, 1); - if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { - g_free(oo); - return NULL; - } - - osx_output_configure(oo, param); - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); - - return &oo->base; -} - -static void -osx_output_finish(struct audio_output *ao) -{ - struct osx_output *od = (struct osx_output *)ao; - - g_mutex_free(od->mutex); - g_cond_free(od->condition); - g_free(od); -} - -static bool -osx_output_set_device(struct osx_output *oo, GError **error) -{ - bool ret = true; - OSStatus status; - UInt32 size, numdevices; - AudioDeviceID *deviceids = NULL; - char name[256]; - unsigned int i; - - if (oo->component_subtype != kAudioUnitSubType_HALOutput) - goto done; - - /* how many audio devices are there? */ - status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, - &size, - NULL); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine number of OS X audio devices: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - /* what are the available audio device IDs? */ - numdevices = size / sizeof(AudioDeviceID); - deviceids = g_malloc(size); - status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, - &size, - deviceids); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine OS X audio device IDs: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - /* which audio device matches oo->device_name? */ - for (i = 0; i < numdevices; i++) { - size = sizeof(name); - status = AudioDeviceGetProperty(deviceids[i], 0, false, - kAudioDevicePropertyDeviceName, - &size, name); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine OS X device name " - "(device %u): %s", - (unsigned int) deviceids[i], - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - if (strcmp(oo->device_name, name) == 0) { - g_debug("found matching device: ID=%u, name=%s", - (unsigned int) deviceids[i], name); - break; - } - } - if (i == numdevices) { - g_warning("Found no audio device with name '%s' " - "(will use default audio device)", - oo->device_name); - goto done; - } - - status = AudioUnitSetProperty(oo->au, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &(deviceids[i]), - sizeof(AudioDeviceID)); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to set OS X audio output device: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - g_debug("set OS X audio output device ID=%u, name=%s", - (unsigned int) deviceids[i], name); - -done: - if (deviceids != NULL) - g_free(deviceids); - return ret; -} - -static OSStatus -osx_render(void *vdata, - G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags, - G_GNUC_UNUSED const AudioTimeStamp *in_timestamp, - G_GNUC_UNUSED UInt32 in_bus_number, - G_GNUC_UNUSED UInt32 in_number_frames, - AudioBufferList *buffer_list) -{ - struct osx_output *od = (struct osx_output *) vdata; - AudioBuffer *buffer = &buffer_list->mBuffers[0]; - size_t buffer_size = buffer->mDataByteSize; - - assert(od->buffer != NULL); - - g_mutex_lock(od->mutex); - - size_t nbytes; - const void *src = fifo_buffer_read(od->buffer, &nbytes); - - if (src != NULL) { - if (nbytes > buffer_size) - nbytes = buffer_size; - - memcpy(buffer->mData, src, nbytes); - fifo_buffer_consume(od->buffer, nbytes); - } else - nbytes = 0; - - g_cond_signal(od->condition); - g_mutex_unlock(od->mutex); - - buffer->mDataByteSize = nbytes; - - unsigned i; - for (i = 1; i < buffer_list->mNumberBuffers; ++i) { - buffer = &buffer_list->mBuffers[i]; - buffer->mDataByteSize = 0; - } - - return 0; -} - -static bool -osx_output_enable(struct audio_output *ao, GError **error_r) -{ - struct osx_output *oo = (struct osx_output *)ao; - - ComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = oo->component_subtype; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - Component comp = FindNextComponent(NULL, &desc); - if (comp == 0) { - g_set_error(error_r, osx_output_quark(), 0, - "Error finding OS X component"); - return false; - } - - OSStatus status = OpenAComponent(comp, &oo->au); - if (status != noErr) { - g_set_error(error_r, osx_output_quark(), status, - "Unable to open OS X component: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - if (!osx_output_set_device(oo, error_r)) { - CloseComponent(oo->au); - return false; - } - - AURenderCallbackStruct callback; - callback.inputProc = osx_render; - callback.inputProcRefCon = oo; - - ComponentResult result = - AudioUnitSetProperty(oo->au, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, - &callback, sizeof(callback)); - if (result != noErr) { - CloseComponent(oo->au); - g_set_error(error_r, osx_output_quark(), result, - "unable to set callback for OS X audio unit"); - return false; - } - - return true; -} - -static void -osx_output_disable(struct audio_output *ao) -{ - struct osx_output *oo = (struct osx_output *)ao; - - CloseComponent(oo->au); -} - -static void -osx_output_cancel(struct audio_output *ao) -{ - struct osx_output *od = (struct osx_output *)ao; - - g_mutex_lock(od->mutex); - fifo_buffer_clear(od->buffer); - g_mutex_unlock(od->mutex); -} - -static void -osx_output_close(struct audio_output *ao) -{ - struct osx_output *od = (struct osx_output *)ao; - - AudioOutputUnitStop(od->au); - AudioUnitUninitialize(od->au); - - fifo_buffer_free(od->buffer); -} - -static bool -osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct osx_output *od = (struct osx_output *)ao; - - AudioStreamBasicDescription stream_description; - stream_description.mSampleRate = audio_format->sample_rate; - stream_description.mFormatID = kAudioFormatLinearPCM; - stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - - switch (audio_format->format) { - case SAMPLE_FORMAT_S8: - stream_description.mBitsPerChannel = 8; - break; - - case SAMPLE_FORMAT_S16: - stream_description.mBitsPerChannel = 16; - break; - - case SAMPLE_FORMAT_S32: - stream_description.mBitsPerChannel = 32; - break; - - default: - audio_format->format = SAMPLE_FORMAT_S32; - stream_description.mBitsPerChannel = 32; - break; - } - -#if G_BYTE_ORDER == G_BIG_ENDIAN - stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; -#endif - - stream_description.mBytesPerPacket = - audio_format_frame_size(audio_format); - stream_description.mFramesPerPacket = 1; - stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; - stream_description.mChannelsPerFrame = audio_format->channels; - - ComponentResult result = - AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, 0, - &stream_description, - sizeof(stream_description)); - if (result != noErr) { - g_set_error(error, osx_output_quark(), result, - "Unable to set format on OS X device"); - return false; - } - - OSStatus status = AudioUnitInitialize(od->au); - if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format->sample_rate * - audio_format_frame_size(audio_format)); - - status = AudioOutputUnitStart(od->au); - if (status != 0) { - AudioUnitUninitialize(od->au); - g_set_error(error, osx_output_quark(), status, - "unable to start audio output: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - return true; -} - -static size_t -osx_output_play(struct audio_output *ao, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct osx_output *od = (struct osx_output *)ao; - - g_mutex_lock(od->mutex); - - void *dest; - size_t max_length; - - while (true) { - dest = fifo_buffer_write(od->buffer, &max_length); - if (dest != NULL) - break; - - /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); - } - - if (size > max_length) - size = max_length; - - memcpy(dest, chunk, size); - fifo_buffer_append(od->buffer, size); - - g_mutex_unlock(od->mutex); - - return size; -} - -const struct audio_output_plugin osx_output_plugin = { - .name = "osx", - .test_default_device = osx_output_test_default_device, - .init = osx_output_init, - .finish = osx_output_finish, - .enable = osx_output_enable, - .disable = osx_output_disable, - .open = osx_output_open, - .close = osx_output_close, - .play = osx_output_play, - .cancel = osx_output_cancel, -}; diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h deleted file mode 100644 index 814702d4f..000000000 --- a/src/output/osx_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OSX_OUTPUT_PLUGIN_H -#define MPD_OSX_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin osx_output_plugin; - -#endif diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c deleted file mode 100644 index 90c5a5331..000000000 --- a/src/output/pipe_output_plugin.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pipe_output_plugin.h" -#include "output_api.h" - -#include <stdio.h> -#include <errno.h> - -struct pipe_output { - struct audio_output base; - - char *cmd; - FILE *fh; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pipe_output_quark(void) -{ - return g_quark_from_static_string("pipe_output"); -} - -static struct audio_output * -pipe_output_init(const struct config_param *param, - GError **error) -{ - struct pipe_output *pd = g_new(struct pipe_output, 1); - - if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) { - g_free(pd); - return NULL; - } - - pd->cmd = config_dup_block_string(param, "command", NULL); - if (pd->cmd == NULL) { - g_set_error(error, pipe_output_quark(), 0, - "No \"command\" parameter specified"); - return NULL; - } - - return &pd->base; -} - -static void -pipe_output_finish(struct audio_output *ao) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - - g_free(pd->cmd); - ao_base_finish(&pd->base); - g_free(pd); -} - -static bool -pipe_output_open(struct audio_output *ao, - G_GNUC_UNUSED struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - - pd->fh = popen(pd->cmd, "w"); - if (pd->fh == NULL) { - g_set_error(error, pipe_output_quark(), errno, - "Error opening pipe \"%s\": %s", - pd->cmd, g_strerror(errno)); - return false; - } - - return true; -} - -static void -pipe_output_close(struct audio_output *ao) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - - pclose(pd->fh); -} - -static size_t -pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - size_t ret; - - ret = fwrite(chunk, 1, size, pd->fh); - if (ret == 0) - g_set_error(error, pipe_output_quark(), errno, - "Write error on pipe: %s", g_strerror(errno)); - - return ret; -} - -const struct audio_output_plugin pipe_output_plugin = { - .name = "pipe", - .init = pipe_output_init, - .finish = pipe_output_finish, - .open = pipe_output_open, - .close = pipe_output_close, - .play = pipe_output_play, -}; diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h deleted file mode 100644 index 9f014f829..000000000 --- a/src/output/pipe_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PIPE_OUTPUT_PLUGIN_H -#define MPD_PIPE_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin pipe_output_plugin; - -#endif diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c deleted file mode 100644 index e267427df..000000000 --- a/src/output/pulse_output_plugin.c +++ /dev/null @@ -1,955 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 <pulse/version.h> - -#include <assert.h> -#include <stddef.h> - -#define MPD_PULSE_NAME "Music Player Daemon" - -#if !defined(PA_CHECK_VERSION) -/** - * This macro was implemented in libpulse 0.9.16. - */ -#define PA_CHECK_VERSION(a,b,c) false -#endif - -struct pulse_output { - struct audio_output base; - - const char *name; - const char *server; - const char *sink; - - struct pulse_mixer *mixer; - - struct pa_threaded_mainloop *mainloop; - struct pa_context *context; - struct pa_stream *stream; - - size_t writable; - -#if !PA_CHECK_VERSION(0,9,11) - /** - * We need this variable because pa_stream_is_corked() wasn't - * added before 0.9.11. - */ - bool pause; -#endif -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pulse_output_quark(void) -{ - return g_quark_from_static_string("pulse_output"); -} - -void -pulse_output_lock(struct pulse_output *po) -{ - pa_threaded_mainloop_lock(po->mainloop); -} - -void -pulse_output_unlock(struct pulse_output *po) -{ - pa_threaded_mainloop_unlock(po->mainloop); -} - -void -pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) -{ - assert(po != NULL); - 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, - G_GNUC_UNUSED 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) -{ - assert(po != NULL); - assert(po->context != NULL); - - 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; -} - -/** - * Frees and clears the stream. - */ -static void -pulse_output_delete_stream(struct pulse_output *po) -{ - assert(po != NULL); - assert(po->stream != NULL); - -#if PA_CHECK_VERSION(0,9,8) - pa_stream_set_suspended_callback(po->stream, NULL, NULL); -#endif - - pa_stream_set_state_callback(po->stream, NULL, NULL); - pa_stream_set_write_callback(po->stream, NULL, NULL); - - pa_stream_disconnect(po->stream); - pa_stream_unref(po->stream); - po->stream = NULL; -} - -/** - * Frees and clears the context. - * - * Caller must lock the main loop. - */ -static void -pulse_output_delete_context(struct pulse_output *po) -{ - assert(po != NULL); - assert(po->context != NULL); - - pa_context_set_state_callback(po->context, NULL, NULL); - pa_context_set_subscribe_callback(po->context, NULL, NULL); - - pa_context_disconnect(po->context); - pa_context_unref(po->context); - po->context = NULL; -} - -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_context(struct pulse_output *po, GError **error_r) -{ - assert(po != NULL); - assert(po->mainloop != NULL); - - po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), - MPD_PULSE_NAME); - if (po->context == NULL) { - 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)) { - pulse_output_delete_context(po); - return false; - } - - return true; -} - -static struct audio_output * -pulse_output_init(const struct config_param *param, GError **error_r) -{ - struct pulse_output *po; - - g_setenv("PULSE_PROP_media.role", "music", true); - - po = g_new(struct pulse_output, 1); - if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) { - g_free(po); - return NULL; - } - - po->name = config_get_block_string(param, "name", "mpd_pulse"); - po->server = config_get_block_string(param, "server", NULL); - po->sink = config_get_block_string(param, "sink", NULL); - - po->mixer = NULL; - po->mainloop = NULL; - po->context = NULL; - po->stream = NULL; - - return &po->base; -} - -static void -pulse_output_finish(struct audio_output *ao) -{ - struct pulse_output *po = (struct pulse_output *)ao; - - ao_base_finish(&po->base); - g_free(po); -} - -static bool -pulse_output_enable(struct audio_output *ao, GError **error_r) -{ - struct pulse_output *po = (struct pulse_output *)ao; - - 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; - } - - /* create the libpulse context and connect it */ - - 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(struct audio_output *ao) -{ - struct pulse_output *po = (struct pulse_output *)ao; - - assert(po->mainloop != NULL); - - 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. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_connection(struct pulse_output *po, GError **error_r) -{ - assert(po->mainloop != NULL); - - pa_context_state_t state; - - 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 */ - 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); - 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; - } - } -} - -#if PA_CHECK_VERSION(0,9,8) - -static void -pulse_output_stream_suspended_cb(G_GNUC_UNUSED pa_stream *stream, void *userdata) -{ - struct pulse_output *po = userdata; - - assert(stream == po->stream || po->stream == NULL); - assert(po->mainloop != NULL); - - /* wake up the main loop to break out of the loop in - pulse_output_play() */ - pa_threaded_mainloop_signal(po->mainloop, 0); -} - -#endif - -static void -pulse_output_stream_state_cb(pa_stream *stream, void *userdata) -{ - struct pulse_output *po = userdata; - - assert(stream == po->stream || po->stream == NULL); - assert(po->mainloop != NULL); - assert(po->context != NULL); - - switch (pa_stream_get_state(stream)) { - case PA_STREAM_READY: - if (po->mixer != NULL) - 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; - - assert(po->mainloop != NULL); - - po->writable = nbytes; - pa_threaded_mainloop_signal(po->mainloop, 0); -} - -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss, - GError **error_r) -{ - assert(po != NULL); - assert(po->context != NULL); - - po->stream = pa_stream_new(po->context, po->name, ss, NULL); - if (po->stream == NULL) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_stream_new() has failed: %s", - pa_strerror(pa_context_errno(po->context))); - return false; - } - -#if PA_CHECK_VERSION(0,9,8) - pa_stream_set_suspended_callback(po->stream, - pulse_output_stream_suspended_cb, po); -#endif - - pa_stream_set_state_callback(po->stream, - pulse_output_stream_state_cb, po); - pa_stream_set_write_callback(po->stream, - pulse_output_stream_write_cb, po); - - return true; -} - -static bool -pulse_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) -{ - struct pulse_output *po = (struct pulse_output *)ao; - pa_sample_spec ss; - int error; - - assert(po->mainloop != NULL); - - pa_threaded_mainloop_lock(po->mainloop); - - 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)) { - pa_threaded_mainloop_unlock(po->mainloop); - 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; - - /* create a stream .. */ - - if (!pulse_output_setup_stream(po, &ss, error_r)) { - pa_threaded_mainloop_unlock(po->mainloop); - return false; - } - - /* .. and connect it (asynchronously) */ - - error = pa_stream_connect_playback(po->stream, po->sink, - NULL, 0, NULL, NULL); - if (error < 0) { - pulse_output_delete_stream(po); - - 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(struct audio_output *ao) -{ - struct pulse_output *po = (struct pulse_output *)ao; - pa_operation *o; - - assert(po->mainloop != NULL); - - 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); - } - - pulse_output_delete_stream(po); - - 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 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) -{ - while (true) { - switch (pa_stream_get_state(po->stream)) { - 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(), - pa_context_errno(po->context), - "failed to connect the stream: %s", - pa_strerror(pa_context_errno(po->context))); - return false; - - case PA_STREAM_CREATING: - pa_threaded_mainloop_wait(po->mainloop); - break; - } - } -} - -/** - * 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->mainloop != NULL); - assert(po->context != NULL); - 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 unsigned -pulse_output_delay(struct audio_output *ao) -{ - struct pulse_output *po = (struct pulse_output *)ao; - unsigned result = 0; - - pa_threaded_mainloop_lock(po->mainloop); - - if (po->base.pause && pulse_output_stream_is_paused(po) && - pa_stream_get_state(po->stream) == PA_STREAM_READY) - /* idle while paused */ - result = 1000; - - pa_threaded_mainloop_unlock(po->mainloop); - - return result; -} - -static size_t -pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct pulse_output *po = (struct pulse_output *)ao; - int error; - - assert(po->mainloop != NULL); - 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)) { - pa_threaded_mainloop_unlock(po->mainloop); - return 0; - } - - /* wait until the server allows us to write */ - - while (po->writable == 0) { -#if PA_CHECK_VERSION(0,9,8) - if (pa_stream_is_suspended(po->stream)) { - pa_threaded_mainloop_unlock(po->mainloop); - g_set_error(error_r, pulse_output_quark(), 0, - "suspended"); - return 0; - } -#endif - - pa_threaded_mainloop_wait(po->mainloop); - - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { - pa_threaded_mainloop_unlock(po->mainloop); - g_set_error(error_r, pulse_output_quark(), 0, - "disconnected"); - return 0; - } - } - - /* 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(struct audio_output *ao) -{ - struct pulse_output *po = (struct pulse_output *)ao; - pa_operation *o; - - assert(po->mainloop != NULL); - 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(struct audio_output *ao) -{ - struct pulse_output *po = (struct pulse_output *)ao; - GError *error = NULL; - - assert(po->mainloop != 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) && - !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 = (struct pulse_output *)pulse_output_init(NULL, NULL); - if (po == NULL) - return false; - - success = pulse_output_wait_connection(po, NULL); - pulse_output_finish(&po->base); - - 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, - .delay = pulse_output_delay, - .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 deleted file mode 100644 index 02a51f27b..000000000 --- a/src/output/pulse_output_plugin.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PULSE_OUTPUT_PLUGIN_H -#define MPD_PULSE_OUTPUT_PLUGIN_H - -#include <stdbool.h> - -#include <glib.h> - -struct pulse_output; -struct pulse_mixer; -struct pa_cvolume; - -extern const struct audio_output_plugin pulse_output_plugin; - -void -pulse_output_lock(struct pulse_output *po); - -void -pulse_output_unlock(struct pulse_output *po); - -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/recorder_output_plugin.c b/src/output/recorder_output_plugin.c deleted file mode 100644 index b84cb244c..000000000 --- a/src/output/recorder_output_plugin.c +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "recorder_output_plugin.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 { - struct audio_output base; - - /** - * 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 struct audio_output * -recorder_output_init(const struct config_param *param, GError **error_r) -{ - struct recorder_output *recorder = g_new(struct recorder_output, 1); - if (!ao_base_init(&recorder->base, &recorder_output_plugin, param, - error_r)) { - g_free(recorder); - return NULL; - } - - /* read configuration */ - - const char *encoder_name = - config_get_block_string(param, "encoder", "vorbis"); - const struct encoder_plugin *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); - goto failure; - } - - recorder->path = config_get_block_string(param, "path", NULL); - if (recorder->path == NULL) { - g_set_error(error_r, recorder_output_quark(), 0, - "'path' not configured"); - goto failure; - } - - /* initialize encoder */ - - recorder->encoder = encoder_init(encoder_plugin, param, error_r); - if (recorder->encoder == NULL) - goto failure; - - return &recorder->base; - -failure: - ao_base_finish(&recorder->base); - g_free(recorder); - return NULL; -} - -static void -recorder_output_finish(struct audio_output *ao) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - encoder_finish(recorder->encoder); - ao_base_finish(&recorder->base); - g_free(recorder); -} - -static bool -recorder_write_to_file(struct recorder_output *recorder, - const void *_data, size_t length, - GError **error_r) -{ - assert(length > 0); - - const int fd = recorder->fd; - - const uint8_t *data = (const uint8_t *)_data, *end = data + length; - - while (true) { - ssize_t nbytes = write(fd, data, end - data); - if (nbytes > 0) { - data += nbytes; - if (data == end) - 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; - } - } -} - -/** - * Writes pending data from the encoder to the output file. - */ -static bool -recorder_output_encoder_to_file(struct recorder_output *recorder, - GError **error_r) -{ - assert(recorder->fd >= 0); - - while (true) { - /* read from the encoder */ - - size_t size = encoder_read(recorder->encoder, recorder->buffer, - sizeof(recorder->buffer)); - if (size == 0) - return true; - - /* write everything into the file */ - - if (!recorder_write_to_file(recorder, recorder->buffer, size, - error_r)) - return false; - } -} - -static bool -recorder_output_open(struct audio_output *ao, - struct audio_format *audio_format, - GError **error_r) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - /* 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 */ - - if (!encoder_open(recorder->encoder, audio_format, error_r)) { - close(recorder->fd); - unlink(recorder->path); - return false; - } - - if (!recorder_output_encoder_to_file(recorder, error_r)) { - encoder_close(recorder->encoder); - close(recorder->fd); - unlink(recorder->path); - return false; - } - - return true; -} - -static void -recorder_output_close(struct audio_output *ao) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - /* flush the encoder and write the rest to the file */ - - if (encoder_end(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(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - return encoder_write(recorder->encoder, chunk, size, error_r) && - recorder_output_encoder_to_file(recorder, error_r) - ? 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/recorder_output_plugin.h b/src/output/recorder_output_plugin.h deleted file mode 100644 index a9bf755bd..000000000 --- a/src/output/recorder_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_RECORDER_OUTPUT_PLUGIN_H -#define MPD_RECORDER_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin recorder_output_plugin; - -#endif diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c deleted file mode 100644 index 1c2c48321..000000000 --- a/src/output/roar_output_plugin.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "roar_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "roar_output_plugin.h" - -#include <glib.h> -#include <stdint.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> - -#include <roaraudio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "roaraudio" - -typedef struct roar -{ - struct audio_output base; - - roar_vs_t * vss; - int err; - char *host; - char *name; - int role; - struct roar_connection con; - struct roar_audio_info info; - GMutex *lock; - volatile bool alive; -} roar_t; - -static inline GQuark -roar_output_quark(void) -{ - return g_quark_from_static_string("roar_output"); -} - -static int -roar_output_get_volume_locked(struct roar *roar) -{ - if (roar->vss == NULL || !roar->alive) - return -1; - - float l, r; - int error; - if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) - return -1; - - return (l + r) * 50; -} - -int -roar_output_get_volume(struct roar *roar) -{ - g_mutex_lock(roar->lock); - int volume = roar_output_get_volume_locked(roar); - g_mutex_unlock(roar->lock); - return volume; -} - -static bool -roar_output_set_volume_locked(struct roar *roar, unsigned volume) -{ - assert(volume <= 100); - - if (roar->vss == NULL || !roar->alive) - return false; - - int error; - float level = volume / 100.0; - - roar_vs_volume_mono(roar->vss, level, &error); - return true; -} - -bool -roar_output_set_volume(struct roar *roar, unsigned volume) -{ - g_mutex_lock(roar->lock); - bool success = roar_output_set_volume_locked(roar, volume); - g_mutex_unlock(roar->lock); - return success; -} - -static void -roar_configure(struct roar * self, const struct config_param *param) -{ - self->host = config_dup_block_string(param, "server", NULL); - self->name = config_dup_block_string(param, "name", "MPD"); - - const char *role = config_get_block_string(param, "role", "music"); - self->role = role != NULL - ? roar_str2role(role) - : ROAR_ROLE_MUSIC; -} - -static struct audio_output * -roar_init(const struct config_param *param, GError **error_r) -{ - struct roar *self = g_new0(struct roar, 1); - - if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { - g_free(self); - return NULL; - } - - self->lock = g_mutex_new(); - self->err = ROAR_ERROR_NONE; - roar_configure(self, param); - return &self->base; -} - -static void -roar_finish(struct audio_output *ao) -{ - struct roar *self = (struct roar *)ao; - - g_free(self->host); - g_free(self->name); - g_mutex_free(self->lock); - - ao_base_finish(&self->base); - g_free(self); -} - -static void -roar_use_audio_format(struct roar_audio_info *info, - struct audio_format *audio_format) -{ - info->rate = audio_format->sample_rate; - info->channels = audio_format->channels; - info->codec = ROAR_CODEC_PCM_S; - - switch (audio_format->format) { - case SAMPLE_FORMAT_UNDEFINED: - info->bits = 16; - audio_format->format = SAMPLE_FORMAT_S16; - break; - - case SAMPLE_FORMAT_S8: - info->bits = 8; - break; - - case SAMPLE_FORMAT_S16: - info->bits = 16; - break; - - case SAMPLE_FORMAT_S24_P32: - info->bits = 32; - audio_format->format = SAMPLE_FORMAT_S32; - break; - - case SAMPLE_FORMAT_S32: - info->bits = 32; - break; - } -} - -static bool -roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); - - if (roar_simple_connect(&(self->con), self->host, self->name) < 0) - { - g_set_error(error, roar_output_quark(), 0, - "Failed to connect to Roar server"); - g_mutex_unlock(self->lock); - return false; - } - - self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); - - if (self->vss == NULL || self->err != ROAR_ERROR_NONE) - { - g_set_error(error, roar_output_quark(), 0, - "Failed to connect to server"); - g_mutex_unlock(self->lock); - return false; - } - - roar_use_audio_format(&self->info, audio_format); - - if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, - &(self->err)) < 0) - { - g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); - g_mutex_unlock(self->lock); - return false; - } - roar_vs_role(self->vss, self->role, &(self->err)); - self->alive = true; - - g_mutex_unlock(self->lock); - return true; -} - -static void -roar_close(struct audio_output *ao) -{ - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); - self->alive = false; - - if (self->vss != NULL) - roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); - self->vss = NULL; - roar_disconnect(&(self->con)); - g_mutex_unlock(self->lock); -} - -static void -roar_cancel_locked(struct roar *self) -{ - if (self->vss == NULL) - return; - - roar_vs_t *vss = self->vss; - self->vss = NULL; - roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); - self->alive = false; - - vss = roar_vs_new_from_con(&(self->con), &(self->err)); - if (vss == NULL) - return; - - if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, - &(self->err)) < 0) { - roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); - g_warning("Failed to start stream"); - return; - } - - roar_vs_role(vss, self->role, &(self->err)); - self->vss = vss; - self->alive = true; -} - -static void -roar_cancel(struct audio_output *ao) -{ - struct roar *self = (struct roar *)ao; - - g_mutex_lock(self->lock); - roar_cancel_locked(self); - g_mutex_unlock(self->lock); -} - -static size_t -roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) -{ - struct roar *self = (struct roar *)ao; - ssize_t rc; - - if (self->vss == NULL) - { - g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); - return 0; - } - - rc = roar_vs_write(self->vss, chunk, size, &(self->err)); - if ( rc <= 0 ) - { - g_set_error(error, roar_output_quark(), 0, "Failed to play data"); - return 0; - } - - return rc; -} - -static const char* -roar_tag_convert(enum tag_type type, bool *is_uuid) -{ - *is_uuid = false; - switch (type) - { - case TAG_ARTIST: - case TAG_ALBUM_ARTIST: - return "AUTHOR"; - case TAG_ALBUM: - return "ALBUM"; - case TAG_TITLE: - return "TITLE"; - case TAG_TRACK: - return "TRACK"; - case TAG_NAME: - return "NAME"; - case TAG_GENRE: - return "GENRE"; - case TAG_DATE: - return "DATE"; - case TAG_PERFORMER: - return "PERFORMER"; - case TAG_COMMENT: - return "COMMENT"; - case TAG_DISC: - return "DISCID"; - case TAG_COMPOSER: -#ifdef ROAR_META_TYPE_COMPOSER - return "COMPOSER"; -#else - return "AUTHOR"; -#endif - case TAG_MUSICBRAINZ_ARTISTID: - case TAG_MUSICBRAINZ_ALBUMID: - case TAG_MUSICBRAINZ_ALBUMARTISTID: - case TAG_MUSICBRAINZ_TRACKID: - *is_uuid = true; - return "HASH"; - - default: - return NULL; - } -} - -static void -roar_send_tag(struct audio_output *ao, const struct tag *meta) -{ - struct roar *self = (struct roar *)ao; - - if (self->vss == NULL) - return; - - g_mutex_lock(self->lock); - size_t cnt = 1; - struct roar_keyval vals[32]; - memset(vals, 0, sizeof(vals)); - char uuid_buf[32][64]; - - char timebuf[16]; - snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", - meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); - - vals[0].key = g_strdup("LENGTH"); - vals[0].value = timebuf; - - for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) - { - bool is_uuid = false; - const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); - if (key != NULL) - { - if (is_uuid) - { - snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", - meta->items[i]->value); - vals[cnt].key = g_strdup(key); - vals[cnt].value = uuid_buf[cnt]; - } - else - { - vals[cnt].key = g_strdup(key); - vals[cnt].value = meta->items[i]->value; - } - cnt++; - } - } - - roar_vs_meta(self->vss, vals, cnt, &(self->err)); - - for (unsigned i = 0; i < 32; i++) - g_free(vals[i].key); - - g_mutex_unlock(self->lock); -} - -const struct audio_output_plugin roar_output_plugin = { - .name = "roar", - .init = roar_init, - .finish = roar_finish, - .open = roar_open, - .play = roar_play, - .cancel = roar_cancel, - .close = roar_close, - .send_tag = roar_send_tag, - - .mixer_plugin = &roar_mixer_plugin -}; diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h deleted file mode 100644 index 78b628cc4..000000000 --- a/src/output/roar_output_plugin.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ROAR_OUTPUT_PLUGIN_H -#define MPD_ROAR_OUTPUT_PLUGIN_H - -#include <stdbool.h> - -struct roar; - -extern const struct audio_output_plugin roar_output_plugin; - -int -roar_output_get_volume(struct roar *roar); - -bool -roar_output_set_volume(struct roar *roar, unsigned volume); - -#endif diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c deleted file mode 100644 index 56456a0ea..000000000 --- a/src/output/shout_output_plugin.c +++ /dev/null @@ -1,555 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "shout_output_plugin.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "mpd_error.h" - -#include <shout/shout.h> -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "shout" - -#define DEFAULT_CONN_TIMEOUT 2 - -struct shout_data { - struct audio_output base; - - shout_t *shout_conn; - shout_metadata_t *shout_meta; - - struct encoder *encoder; - - float quality; - int bitrate; - - int timeout; - - uint8_t buffer[32768]; -}; - -static int shout_init_count; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -shout_output_quark(void) -{ - return g_quark_from_static_string("shout_output"); -} - -static const struct encoder_plugin * -shout_encoder_plugin_get(const char *name) -{ - if (strcmp(name, "ogg") == 0) - name = "vorbis"; - else if (strcmp(name, "mp3") == 0) - name = "lame"; - - return encoder_plugin_get(name); -} - -static struct shout_data *new_shout_data(void) -{ - struct shout_data *ret = g_new(struct shout_data, 1); - - ret->shout_conn = shout_new(); - ret->shout_meta = shout_metadata_new(); - ret->bitrate = -1; - ret->quality = -2.0; - ret->timeout = DEFAULT_CONN_TIMEOUT; - - return ret; -} - -static void free_shout_data(struct shout_data *sd) -{ - if (sd->shout_meta) - shout_metadata_free(sd->shout_meta); - if (sd->shout_conn) - shout_free(sd->shout_conn); - - g_free(sd); -} - -#define check_block_param(name) { \ - block_param = config_get_block_param(param, name); \ - if (!block_param) { \ - MPD_ERROR("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ - } \ - } - -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) -{ - struct shout_data *sd = new_shout_data(); - if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { - free_shout_data(sd); - return NULL; - } - - const struct audio_format *audio_format = - &sd->base.config_audio_format; - if (!audio_format_fully_defined(audio_format)) { - g_set_error(error, shout_output_quark(), 0, - "Need full audio format specification"); - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; - } - - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - - const struct block_param *block_param; - check_block_param("host"); - char *host = block_param->value; - - check_block_param("mount"); - char *mount = block_param->value; - - unsigned port = config_get_block_unsigned(param, "port", 0); - if (port == 0) { - g_set_error(error, shout_output_quark(), 0, - "shout port must be configured"); - goto failure; - } - - check_block_param("password"); - const char *passwd = block_param->value; - - check_block_param("name"); - const char *name = block_param->value; - - bool public = config_get_block_bool(param, "public", false); - - const char *user = config_get_block_string(param, "user", "source"); - - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { - char *test; - sd->quality = strtod(value, &test); - - if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { - g_set_error(error, shout_output_quark(), 0, - "shout quality \"%s\" is not a number in the " - "range -1 to 10, line %i", - value, param->line); - goto failure; - } - - if (config_get_block_string(param, "bitrate", NULL) != NULL) { - g_set_error(error, shout_output_quark(), 0, - "quality and bitrate are " - "both defined"); - goto failure; - } - } else { - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { - g_set_error(error, shout_output_quark(), 0, - "neither bitrate nor quality defined"); - goto failure; - } - - char *test; - sd->bitrate = strtol(value, &test, 10); - - if (*test != '\0' || sd->bitrate <= 0) { - g_set_error(error, shout_output_quark(), 0, - "bitrate must be a positive integer"); - goto failure; - } - } - - const char *encoding = config_get_block_string(param, "encoding", - "ogg"); - const struct encoder_plugin *encoder_plugin = - shout_encoder_plugin_get(encoding); - if (encoder_plugin == NULL) { - g_set_error(error, shout_output_quark(), 0, - "couldn't find shout encoder plugin \"%s\"", - encoding); - goto failure; - } - - sd->encoder = encoder_init(encoder_plugin, param, error); - if (sd->encoder == NULL) - goto failure; - - unsigned shout_format; - if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) - shout_format = SHOUT_FORMAT_MP3; - else - shout_format = SHOUT_FORMAT_OGG; - - unsigned protocol; - value = config_get_block_string(param, "protocol", NULL); - if (value != NULL) { - if (0 == strcmp(value, "shoutcast") && - 0 != strcmp(encoding, "mp3")) { - g_set_error(error, shout_output_quark(), 0, - "you cannot stream \"%s\" to shoutcast, use mp3", - encoding); - goto failure; - } else if (0 == strcmp(value, "shoutcast")) - protocol = SHOUT_PROTOCOL_ICY; - else if (0 == strcmp(value, "icecast1")) - protocol = SHOUT_PROTOCOL_XAUDIOCAST; - else if (0 == strcmp(value, "icecast2")) - protocol = SHOUT_PROTOCOL_HTTP; - else { - g_set_error(error, shout_output_quark(), 0, - "shout protocol \"%s\" is not \"shoutcast\" or " - "\"icecast1\"or \"icecast2\"", - value); - goto failure; - } - } else { - protocol = SHOUT_PROTOCOL_HTTP; - } - - if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS || - shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS || - shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS || - shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS || - shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS || - shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS || - shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS || - shout_set_format(sd->shout_conn, shout_format) - != SHOUTERR_SUCCESS || - shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS || - shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - /* optional paramters */ - sd->timeout = config_get_block_unsigned(param, "timeout", - DEFAULT_CONN_TIMEOUT); - - value = config_get_block_string(param, "genre", NULL); - if (value != NULL && shout_set_genre(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - value = config_get_block_string(param, "description", NULL); - if (value != NULL && shout_set_description(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - value = config_get_block_string(param, "url", NULL); - if (value != NULL && shout_set_url(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - { - char temp[11]; - memset(temp, 0, sizeof(temp)); - - snprintf(temp, sizeof(temp), "%u", audio_format->channels); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp); - - snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate); - - shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp); - - if (sd->quality >= -1.0) { - snprintf(temp, sizeof(temp), "%2.2f", sd->quality); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY, - temp); - } else { - snprintf(temp, sizeof(temp), "%d", sd->bitrate); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE, - temp); - } - } - - return &sd->base; - -failure: - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; -} - -static bool -handle_shout_error(struct shout_data *sd, int err, GError **error) -{ - switch (err) { - case SHOUTERR_SUCCESS: - break; - - case SHOUTERR_UNCONNECTED: - case SHOUTERR_SOCKET: - g_set_error(error, shout_output_quark(), err, - "Lost shout connection to %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - - default: - g_set_error(error, shout_output_quark(), err, - "connection to %s:%i error: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } - - return true; -} - -static bool -write_page(struct shout_data *sd, GError **error) -{ - assert(sd->encoder != NULL); - - while (true) { - size_t nbytes = encoder_read(sd->encoder, - sd->buffer, sizeof(sd->buffer)); - if (nbytes == 0) - return true; - - int err = shout_send(sd->shout_conn, sd->buffer, nbytes); - if (!handle_shout_error(sd, err, error)) - return false; - } - - return true; -} - -static void close_shout_conn(struct shout_data * sd) -{ - if (sd->encoder != NULL) { - if (encoder_end(sd->encoder, NULL)) - write_page(sd, NULL); - - encoder_close(sd->encoder); - } - - if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && - shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { - g_warning("problem closing connection to shout server: %s\n", - shout_get_error(sd->shout_conn)); - } -} - -static void -my_shout_finish_driver(struct audio_output *ao) -{ - struct shout_data *sd = (struct shout_data *)ao; - - encoder_finish(sd->encoder); - - ao_base_finish(&sd->base); - free_shout_data(sd); - - shout_init_count--; - - if (shout_init_count == 0) - shout_shutdown(); -} - -static void -my_shout_drop_buffered_audio(struct audio_output *ao) -{ - G_GNUC_UNUSED - struct shout_data *sd = (struct shout_data *)ao; - - /* needs to be implemented for shout */ -} - -static void -my_shout_close_device(struct audio_output *ao) -{ - struct shout_data *sd = (struct shout_data *)ao; - - close_shout_conn(sd); -} - -static bool -shout_connect(struct shout_data *sd, GError **error) -{ - switch (shout_open(sd->shout_conn)) { - case SHOUTERR_SUCCESS: - case SHOUTERR_CONNECTED: - return true; - - default: - g_set_error(error, shout_output_quark(), 0, - "problem opening connection to shout server %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } -} - -static bool -my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct shout_data *sd = (struct shout_data *)ao; - - if (!shout_connect(sd, error)) - return false; - - if (!encoder_open(sd->encoder, audio_format, error)) { - shout_close(sd->shout_conn); - return false; - } - - if (!write_page(sd, error)) { - encoder_close(sd->encoder); - shout_close(sd->shout_conn); - return false; - } - - return true; -} - -static unsigned -my_shout_delay(struct audio_output *ao) -{ - struct shout_data *sd = (struct shout_data *)ao; - - int delay = shout_delay(sd->shout_conn); - if (delay < 0) - delay = 0; - - return delay; -} - -static size_t -my_shout_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct shout_data *sd = (struct shout_data *)ao; - - return encoder_write(sd->encoder, chunk, size, error) && - write_page(sd, error) - ? size - : 0; -} - -static bool -my_shout_pause(struct audio_output *ao) -{ - static const char silence[1020]; - - return my_shout_play(ao, silence, sizeof(silence), NULL); -} - -static void -shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) -{ - char artist[size]; - char title[size]; - - artist[0] = 0; - title[0] = 0; - - for (unsigned i = 0; i < tag->num_items; i++) { - switch (tag->items[i]->type) { - case TAG_ARTIST: - strncpy(artist, tag->items[i]->value, size); - break; - case TAG_TITLE: - strncpy(title, tag->items[i]->value, size); - break; - - default: - break; - } - } - - snprintf(dest, size, "%s - %s", artist, title); -} - -static void my_shout_set_tag(struct audio_output *ao, - const struct tag *tag) -{ - struct shout_data *sd = (struct shout_data *)ao; - GError *error = NULL; - - if (sd->encoder->plugin->tag != NULL) { - /* encoder plugin supports stream tags */ - - if (!encoder_pre_tag(sd->encoder, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - if (!write_page(sd, NULL)) - return; - - if (!encoder_tag(sd->encoder, tag, &error)) { - g_warning("%s", error->message); - g_error_free(error); - } - } else { - /* no stream tag support: fall back to icy-metadata */ - char song[1024]; - shout_tag_to_metadata(tag, song, sizeof(song)); - - shout_metadata_add(sd->shout_meta, "song", song); - if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, - sd->shout_meta)) { - g_warning("error setting shout metadata\n"); - } - } - - write_page(sd, NULL); -} - -const struct audio_output_plugin shout_output_plugin = { - .name = "shout", - .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, - .close = my_shout_close_device, - .send_tag = my_shout_set_tag, -}; diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h deleted file mode 100644 index 9a7378803..000000000 --- a/src/output/shout_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SHOUT_OUTPUT_PLUGIN_H -#define MPD_SHOUT_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin shout_output_plugin; - -#endif diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c deleted file mode 100644 index ce726009a..000000000 --- a/src/output/solaris_output_plugin.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "solaris_output_plugin.h" -#include "output_api.h" -#include "fd_util.h" - -#include <glib.h> - -#include <sys/stropts.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> - -#ifdef __sun -#include <sys/audio.h> -#else - -/* some fake declarations that allow build this plugin on systems - other than Solaris, just to see if it compiles */ - -#define AUDIO_GETINFO 0 -#define AUDIO_SETINFO 0 -#define AUDIO_ENCODING_LINEAR 0 - -struct audio_info { - struct { - unsigned sample_rate, channels, precision, encoding; - } play; -}; - -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "solaris_output" - -struct solaris_output { - struct audio_output base; - - /* configuration */ - const char *device; - - int fd; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -solaris_output_quark(void) -{ - return g_quark_from_static_string("solaris_output"); -} - -static bool -solaris_output_test_default_device(void) -{ - struct stat st; - - return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) && - access("/dev/audio", W_OK) == 0; -} - -static struct audio_output * -solaris_output_init(const struct config_param *param, GError **error_r) -{ - struct solaris_output *so = g_new(struct solaris_output, 1); - - if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) { - g_free(so); - return NULL; - } - - so->device = config_get_block_string(param, "device", "/dev/audio"); - - return &so->base; -} - -static void -solaris_output_finish(struct audio_output *ao) -{ - struct solaris_output *so = (struct solaris_output *)ao; - - ao_base_finish(&so->base); - g_free(so); -} - -static bool -solaris_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct solaris_output *so = (struct solaris_output *)ao; - struct audio_info info; - int ret, flags; - - /* support only 16 bit mono/stereo for now; nothing else has - been tested */ - audio_format->format = SAMPLE_FORMAT_S16; - - /* open the device in non-blocking mode */ - - 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", - so->device, g_strerror(errno)); - return false; - } - - /* restore blocking mode */ - - flags = fcntl(so->fd, F_GETFL); - if (flags > 0 && (flags & O_NONBLOCK) != 0) - fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK); - - /* configure the audio device */ - - ret = ioctl(so->fd, AUDIO_GETINFO, &info); - if (ret < 0) { - g_set_error(error, solaris_output_quark(), errno, - "AUDIO_GETINFO failed: %s", g_strerror(errno)); - close(so->fd); - return false; - } - - info.play.sample_rate = audio_format->sample_rate; - info.play.channels = audio_format->channels; - info.play.precision = 16; - info.play.encoding = AUDIO_ENCODING_LINEAR; - - ret = ioctl(so->fd, AUDIO_SETINFO, &info); - if (ret < 0) { - g_set_error(error, solaris_output_quark(), errno, - "AUDIO_SETINFO failed: %s", g_strerror(errno)); - close(so->fd); - return false; - } - - return true; -} - -static void -solaris_output_close(struct audio_output *ao) -{ - struct solaris_output *so = (struct solaris_output *)ao; - - close(so->fd); -} - -static size_t -solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct solaris_output *so = (struct solaris_output *)ao; - ssize_t nbytes; - - nbytes = write(so->fd, chunk, size); - if (nbytes <= 0) { - g_set_error(error, solaris_output_quark(), errno, - "Write failed: %s", g_strerror(errno)); - return 0; - } - - return nbytes; -} - -static void -solaris_output_cancel(struct audio_output *ao) -{ - struct solaris_output *so = (struct solaris_output *)ao; - - ioctl(so->fd, I_FLUSH); -} - -const struct audio_output_plugin solaris_output_plugin = { - .name = "solaris", - .test_default_device = solaris_output_test_default_device, - .init = solaris_output_init, - .finish = solaris_output_finish, - .open = solaris_output_open, - .close = solaris_output_close, - .play = solaris_output_play, - .cancel = solaris_output_cancel, -}; diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h deleted file mode 100644 index 600aea8c2..000000000 --- a/src/output/solaris_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_H -#define MPD_SOLARIS_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin solaris_output_plugin; - -#endif diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c deleted file mode 100644 index 4d95834b9..000000000 --- a/src/output/winmm_output_plugin.c +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "winmm_output_plugin.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 { - struct audio_output base; - - 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 bool -get_device_id(const char *device_name, UINT *device_id, GError **error_r) -{ - /* if device is not specified use wave mapper */ - if (device_name == NULL) { - *device_id = WAVE_MAPPER; - return true; - } - - UINT numdevs = waveOutGetNumDevs(); - - /* check for device id */ - char *endptr; - UINT id = strtoul(device_name, &endptr, 0); - if (endptr > device_name && *endptr == 0) { - if (id >= numdevs) - goto fail; - *device_id = id; - return true; - } - - /* check for device name */ - for (UINT i = 0; i < numdevs; i++) { - WAVEOUTCAPS caps; - MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); - if (result != MMSYSERR_NOERROR) - continue; - /* szPname is only 32 chars long, so it is often truncated. - Use partial match to work around this. */ - if (strstr(device_name, caps.szPname) == device_name) { - *device_id = i; - return true; - } - } - -fail: - g_set_error(error_r, winmm_output_quark(), 0, - "device \"%s\" is not found", device_name); - return false; -} - -static struct audio_output * -winmm_output_init(const struct config_param *param, GError **error_r) -{ - struct winmm_output *wo = g_new(struct winmm_output, 1); - if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) { - g_free(wo); - return NULL; - } - - const char *device = config_get_block_string(param, "device", NULL); - if (!get_device_id(device, &wo->device_id, error_r)) { - ao_base_finish(&wo->base); - g_free(wo); - return NULL; - } - - return &wo->base; -} - -static void -winmm_output_finish(struct audio_output *ao) -{ - struct winmm_output *wo = (struct winmm_output *)ao; - - ao_base_finish(&wo->base); - g_free(wo); -} - -static bool -winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) -{ - struct winmm_output *wo = (struct winmm_output *)ao; - - 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_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(struct audio_output *ao) -{ - struct winmm_output *wo = (struct winmm_output *)ao; - - for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) - pcm_buffer_deinit(&wo->buffers[i].buffer); - - 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); - assert(dest != NULL); - - 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(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) -{ - struct winmm_output *wo = (struct winmm_output *)ao; - - /* get the next buffer from the ring and prepare it */ - struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; - 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(struct audio_output *ao) -{ - struct winmm_output *wo = (struct winmm_output *)ao; - - if (!winmm_drain_all_buffers(wo, NULL)) - winmm_stop(wo); -} - -static void -winmm_output_cancel(struct audio_output *ao) -{ - struct winmm_output *wo = (struct winmm_output *)ao; - - 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 deleted file mode 100644 index 0605530e1..000000000 --- a/src/output/winmm_output_plugin.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_WINMM_OUTPUT_PLUGIN_H -#define MPD_WINMM_OUTPUT_PLUGIN_H - -#include "check.h" - -#ifdef ENABLE_WINMM_OUTPUT - -#include <windows.h> - -struct winmm_output; - -extern const struct audio_output_plugin winmm_output_plugin; - -HWAVEOUT winmm_output_get_handle(struct winmm_output*); - -#endif - -#endif diff --git a/src/output_all.c b/src/output_all.c deleted file mode 100644 index f56cd04ee..000000000 --- a/src/output_all.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_all.h" -#include "output_internal.h" -#include "output_control.h" -#include "chunk.h" -#include "conf.h" -#include "pipe.h" -#include "buffer.h" -#include "player_control.h" -#include "mpd_error.h" -#include "notify.h" - -#ifndef NDEBUG -#include "chunk.h" -#endif - -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -static struct audio_format input_audio_format; - -static struct audio_output **audio_outputs; -static unsigned int num_audio_outputs; - -/** - * The #music_buffer object where consumed chunks are returned. - */ -static struct music_buffer *g_music_buffer; - -/** - * The #music_pipe object which feeds all audio outputs. It is filled - * by audio_output_all_play(). - */ -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; -} - -struct audio_output * -audio_output_get(unsigned i) -{ - assert(i < num_audio_outputs); - - assert(audio_outputs[i] != NULL); - - return audio_outputs[i]; -} - -struct audio_output * -audio_output_find(const char *name) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_output_get(i); - - if (strcmp(ao->name, name) == 0) - return ao; - } - - /* name not found */ - return NULL; -} - -static unsigned -audio_output_config_count(void) -{ - unsigned int nr = 0; - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) - nr++; - if (!nr) - nr = 1; /* we'll always have at least one device */ - return nr; -} - -void -audio_output_all_init(struct player_control *pc) -{ - const struct config_param *param = NULL; - unsigned int i; - GError *error = NULL; - - notify_init(&audio_output_client_notify); - - num_audio_outputs = audio_output_config_count(); - audio_outputs = g_new(struct audio_output *, num_audio_outputs); - - for (i = 0; i < num_audio_outputs; i++) - { - unsigned int j; - - param = config_get_next_param(CONF_AUDIO_OUTPUT, param); - - /* only allow param to be NULL if there just one audioOutput */ - assert(param || (num_audio_outputs == 1)); - - struct audio_output *output = audio_output_new(param, pc, &error); - if (output == NULL) { - if (param != NULL) - MPD_ERROR("line %i: %s", - param->line, error->message); - else - MPD_ERROR("%s", error->message); - } - - audio_outputs[i] = output; - - /* require output names to be unique: */ - for (j = 0; j < i; j++) { - if (!strcmp(output->name, audio_outputs[j]->name)) { - MPD_ERROR("output devices with identical " - "names: %s\n", output->name); - } - } - } -} - -void -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]); - } - - g_free(audio_outputs); - audio_outputs = NULL; - num_audio_outputs = 0; - - 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 - * command. - */ -static bool -audio_output_all_finished(void) -{ - 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; -} - -static void audio_output_wait_all(void) -{ - while (!audio_output_all_finished()) - notify_wait(&audio_output_client_notify); -} - -/** - * Signals all audio outputs which are open. - */ -static void -audio_output_allow_play_all(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_allow_play(audio_outputs[i]); -} - -static void -audio_output_reset_reopen(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - if (!ao->open && ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - g_mutex_unlock(ao->mutex); -} - -/** - * Resets the "reopen" flag on all audio devices. MPD should - * immediately retry to open the device instead of waiting for the - * timeout when the user wants to start playback. - */ -static void -audio_output_all_reset_reopen(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - audio_output_reset_reopen(ao); - } -} - -/** - * Opens all output devices which are enabled, but closed. - * - * @return true if there is at least open output device which is open - */ -static bool -audio_output_all_update(void) -{ - unsigned int i; - bool ret = false; - - if (!audio_format_defined(&input_audio_format)) - return false; - - for (i = 0; i < num_audio_outputs; ++i) - ret = audio_output_update(audio_outputs[i], - &input_audio_format, g_mp) || ret; - - return ret; -} - -bool -audio_output_all_play(struct music_chunk *chunk) -{ - bool ret; - unsigned int i; - - assert(g_music_buffer != NULL); - assert(g_mp != NULL); - assert(chunk != NULL); - assert(music_chunk_check_format(chunk, &input_audio_format)); - - ret = audio_output_all_update(); - if (!ret) - return false; - - music_pipe_push(g_mp, chunk); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_play(audio_outputs[i]); - - return true; -} - -bool -audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer) -{ - bool ret = false, enabled = false; - unsigned int i; - - assert(audio_format != NULL); - assert(buffer != NULL); - assert(g_music_buffer == NULL || g_music_buffer == buffer); - assert((g_mp == NULL) == (g_music_buffer == NULL)); - - g_music_buffer = buffer; - - /* the audio format must be the same as existing chunks in the - pipe */ - assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format)); - - if (g_mp == NULL) - g_mp = music_pipe_new(); - else - /* if the pipe hasn't been cleared, the the audio - format must not have changed */ - assert(music_pipe_empty(g_mp) || - audio_format_equals(audio_format, - &input_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) { - if (audio_outputs[i]->enabled) - enabled = true; - - if (audio_outputs[i]->open) - ret = true; - } - - if (!enabled) - g_warning("All audio outputs are disabled"); - - if (!ret) - /* close all devices if there was an error */ - audio_output_all_close(); - - return ret; -} - -/** - * Has the specified audio output already consumed this chunk? - */ -static bool -chunk_is_consumed_in(const struct audio_output *ao, - const struct music_chunk *chunk) -{ - if (!ao->open) - return true; - - if (ao->chunk == NULL) - return false; - - assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk)); - - if (chunk != ao->chunk) { - assert(chunk->next != NULL); - return true; - } - - return ao->chunk_finished && chunk->next == NULL; -} - -/** - * Has this chunk been consumed by all audio outputs? - */ -static bool -chunk_is_consumed(const struct music_chunk *chunk) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - const struct audio_output *ao = audio_outputs[i]; - bool consumed; - - g_mutex_lock(ao->mutex); - consumed = chunk_is_consumed_in(ao, chunk); - g_mutex_unlock(ao->mutex); - - if (!consumed) - return false; - } - - return true; -} - -/** - * There's only one chunk left in the pipe (#g_mp), and all audio - * outputs have consumed it already. Clear the reference. - */ -static void -clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked) -{ - assert(chunk->next == NULL); - assert(music_pipe_contains(g_mp, chunk)); - - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - /* this mutex will be unlocked by the caller when it's - ready */ - g_mutex_lock(ao->mutex); - locked[i] = ao->open; - - if (!locked[i]) { - g_mutex_unlock(ao->mutex); - continue; - } - - assert(ao->chunk == chunk); - assert(ao->chunk_finished); - ao->chunk = NULL; - } -} - -unsigned -audio_output_all_check(void) -{ - const struct music_chunk *chunk; - bool is_tail; - struct music_chunk *shifted; - bool locked[num_audio_outputs]; - - assert(g_music_buffer != NULL); - assert(g_mp != NULL); - - while ((chunk = music_pipe_peek(g_mp)) != NULL) { - assert(!music_pipe_empty(g_mp)); - - if (!chunk_is_consumed(chunk)) - /* at least one output is not finished playing - 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 - chunk reference in all outputs */ - clear_tail_chunk(chunk, locked); - - /* remove the chunk from the pipe */ - shifted = music_pipe_shift(g_mp); - assert(shifted == chunk); - - if (is_tail) - /* unlock all audio outputs which were locked - by clear_tail_chunk() */ - for (unsigned i = 0; i < num_audio_outputs; ++i) - if (locked[i]) - g_mutex_unlock(audio_outputs[i]->mutex); - - /* return the chunk to the buffer */ - music_buffer_return(g_music_buffer, shifted); - } - - return 0; -} - -bool -audio_output_all_wait(struct player_control *pc, unsigned threshold) -{ - player_lock(pc); - - if (audio_output_all_check() < threshold) { - player_unlock(pc); - return true; - } - - player_wait(pc); - player_unlock(pc); - - return audio_output_all_check() < threshold; -} - -void -audio_output_all_pause(void) -{ - unsigned int i; - - audio_output_all_update(); - - for (i = 0; i < num_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(); -} - -void -audio_output_all_cancel(void) -{ - unsigned int i; - - /* send the cancel() command to all audio outputs */ - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_cancel(audio_outputs[i]); - - audio_output_wait_all(); - - /* clear the music pipe and return all chunks to the buffer */ - - 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_allow_play_all(); - - /* invalidate elapsed_time */ - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_close(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_close(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_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 deleted file mode 100644 index 4eeb94f13..000000000 --- a/src/output_all.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for dealing with all configured (enabled) audion outputs - * at once. - * - */ - -#ifndef OUTPUT_ALL_H -#define OUTPUT_ALL_H - -#include <stdbool.h> -#include <stddef.h> - -struct audio_format; -struct music_buffer; -struct music_chunk; -struct player_control; - -/** - * Global initialization: load audio outputs from the configuration - * file and initialize them. - */ -void -audio_output_all_init(struct player_control *pc); - -/** - * Global finalization: free memory occupied by audio outputs. All - */ -void -audio_output_all_finish(void); - -/** - * Returns the total number of audio output devices, including those - * who are disabled right now. - */ -unsigned int audio_output_count(void); - -/** - * Returns the "i"th audio output device. - */ -struct audio_output * -audio_output_get(unsigned i); - -/** - * Returns the audio output device with the specified name. Returns - * NULL if the name does not exist. - */ -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 - * the previous format - * @param buffer the #music_buffer where consumed #music_chunk objects - * should be returned - * @return true on success, false on failure - */ -bool -audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer); - -/** - * Closes all audio outputs. - */ -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. - * - * @param chunk the #music_chunk object to be played - * @return true on success, false if no audio output was able to play - * (all closed then) - */ -bool -audio_output_all_play(struct music_chunk *chunk); - -/** - * Checks if the output devices have drained their music pipe, and - * returns the consumed music chunks to the #music_buffer. - * - * @return the number of chunks to play left in the #music_pipe - */ -unsigned -audio_output_all_check(void); - -/** - * Checks if the size of the #music_pipe is below the #threshold. If - * not, it attempts to synchronize with all output threads, and waits - * until another #music_chunk is finished. - * - * @param threshold the maximum number of chunks in the pipe - * @return true if there are less than #threshold chunks in the pipe - */ -bool -audio_output_all_wait(struct player_control *pc, unsigned threshold); - -/** - * Puts all audio outputs into pause mode. Most implementations will - * simply close it then. - */ -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 deleted file mode 100644 index dfeef3518..000000000 --- a/src/output_api.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_API_H -#define MPD_OUTPUT_API_H - -#include "output_plugin.h" -#include "output_internal.h" -#include "audio_format.h" -#include "tag.h" -#include "conf.h" - -#endif diff --git a/src/output_command.c b/src/output_command.c deleted file mode 100644 index 3988f350a..000000000 --- a/src/output_command.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#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) -{ - struct audio_output *ao; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (ao->enabled) - return true; - - ao->enabled = true; - idle_add(IDLE_OUTPUT); - - pc_update_audio(ao->player_control); - - ++audio_output_state_version; - - return true; -} - -bool -audio_output_disable_index(unsigned idx) -{ - struct audio_output *ao; - struct mixer *mixer; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (!ao->enabled) - return true; - - ao->enabled = false; - idle_add(IDLE_OUTPUT); - - mixer = ao->mixer; - if (mixer != NULL) { - mixer_close(mixer); - idle_add(IDLE_MIXER); - } - - pc_update_audio(ao->player_control); - - ++audio_output_state_version; - - return true; -} diff --git a/src/output_command.h b/src/output_command.h deleted file mode 100644 index eda30acc8..000000000 --- a/src/output_command.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#ifndef OUTPUT_COMMAND_H -#define OUTPUT_COMMAND_H - -#include <stdbool.h> - -/** - * Enables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_enable_index(unsigned idx); - -/** - * Disables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_disable_index(unsigned idx); - -#endif diff --git a/src/output_control.c b/src/output_control.c deleted file mode 100644 index 7b95be49b..000000000 --- a/src/output_control.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_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> - -enum { - /** after a failure, wait this number of seconds before - automatically reopening the device */ - REOPEN_AFTER = 10, -}; - -struct notify audio_output_client_notify; - -/** - * Waits for command completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_wait(struct audio_output *ao) -{ - while (ao->command != AO_COMMAND_NONE) { - g_mutex_unlock(ao->mutex); - notify_wait(&audio_output_client_notify); - g_mutex_lock(ao->mutex); - } -} - -/** - * Sends a command to the #audio_output object, but does not wait for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) -{ - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - g_cond_signal(ao->cond); -} - -/** - * Sends a command to the #audio_output object and waits for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void -ao_command(struct audio_output *ao, enum audio_output_command cmd) -{ - ao_command_async(ao, cmd); - ao_command_wait(ao); -} - -/** - * Lock the #audio_output object and execute the command - * synchronously. - */ -static void -ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) -{ - g_mutex_lock(ao->mutex); - ao_command(ao, cmd); - g_mutex_unlock(ao->mutex); -} - -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); - } - - ao_lock_command(ao, AO_COMMAND_ENABLE); -} - -void -audio_output_disable(struct audio_output *ao) -{ - 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; - } - - ao_lock_command(ao, AO_COMMAND_DISABLE); -} - -/** - * Object must be locked (and unlocked) by the caller. - */ -static bool -audio_output_open(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp) -{ - bool open; - - assert(ao != NULL); - assert(ao->allow_play); - assert(audio_format_valid(audio_format)); - assert(mp != NULL); - - if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - if (ao->open && - audio_format_equals(audio_format, &ao->in_audio_format)) { - 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 - to flush the device buffer anyway */ - - /* we're not using audio_output_cancel() here, - because that function is asynchronous */ - ao_command(ao, AO_COMMAND_CANCEL); - } - - return true; - } - - ao->in_audio_format = *audio_format; - ao->chunk = NULL; - - 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->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); - assert(ao->allow_play); - - 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, - const struct music_pipe *mp) -{ - assert(mp != NULL); - - g_mutex_lock(ao->mutex); - - if (ao->enabled && ao->really_enabled) { - if (ao->fail_timer == NULL || - g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { - bool success = audio_output_open(ao, audio_format, mp); - g_mutex_unlock(ao->mutex); - 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) -{ - g_mutex_lock(ao->mutex); - - assert(ao->allow_play); - - if (audio_output_is_open(ao)) - g_cond_signal(ao->cond); - - g_mutex_unlock(ao->mutex); -} - -void audio_output_pause(struct audio_output *ao) -{ - if (ao->mixer != NULL && ao->plugin->pause == NULL) - /* the device has no pause mode: close the mixer, - unless its "global" flag is set (checked by - mixer_auto_close()) */ - mixer_auto_close(ao->mixer); - - g_mutex_lock(ao->mutex); - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_PAUSE); - g_mutex_unlock(ao->mutex); -} - -void -audio_output_drain_async(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_DRAIN); - g_mutex_unlock(ao->mutex); -} - -void audio_output_cancel(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - if (audio_output_is_open(ao)) { - ao->allow_play = false; - ao_command_async(ao, AO_COMMAND_CANCEL); - } - - g_mutex_unlock(ao->mutex); -} - -void -audio_output_allow_play(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - ao->allow_play = true; - if (audio_output_is_open(ao)) - g_cond_signal(ao->cond); - - g_mutex_unlock(ao->mutex); -} - -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) -{ - assert(ao != NULL); - assert(!ao->open || ao->fail_timer == NULL); - - g_mutex_lock(ao->mutex); - audio_output_close_locked(ao); - g_mutex_unlock(ao->mutex); -} - -void audio_output_finish(struct audio_output *ao) -{ - audio_output_close(ao); - - assert(ao->fail_timer == NULL); - - if (ao->thread != NULL) { - assert(ao->allow_play); - ao_lock_command(ao, AO_COMMAND_KILL); - g_thread_join(ao->thread); - ao->thread = NULL; - } - - audio_output_free(ao); -} diff --git a/src/output_control.h b/src/output_control.h deleted file mode 100644 index 874a53518..000000000 --- a/src/output_control.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_CONTROL_H -#define MPD_OUTPUT_CONTROL_H - -#include <glib.h> - -#include <stddef.h> -#include <stdbool.h> - -struct audio_output; -struct audio_format; -struct config_param; -struct music_pipe; -struct player_control; - -static inline GQuark -audio_output_quark(void) -{ - return g_quark_from_static_string("audio_output"); -} - -/** - * 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. - * - * @return true if the device is open - */ -bool -audio_output_update(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp); - -void -audio_output_play(struct audio_output *ao); - -void audio_output_pause(struct audio_output *ao); - -void -audio_output_drain_async(struct audio_output *ao); - -/** - * Clear the "allow_play" flag and send the "CANCEL" command - * asynchronously. To finish the operation, the caller has to call - * audio_output_allow_play(). - */ -void audio_output_cancel(struct audio_output *ao); - -/** - * Set the "allow_play" and signal the thread. - */ -void -audio_output_allow_play(struct audio_output *ao); - -void audio_output_close(struct audio_output *ao); - -/** - * 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_finish.c b/src/output_finish.c deleted file mode 100644 index e11b43675..000000000 --- a/src/output_finish.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "mixer_control.h" -#include "filter_plugin.h" - -#include <assert.h> - -void -ao_base_finish(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->thread == NULL); - - if (ao->mixer != NULL) - mixer_free(ao->mixer); - - g_cond_free(ao->cond); - g_mutex_free(ao->mutex); - - if (ao->replay_gain_filter != NULL) - filter_free(ao->replay_gain_filter); - - if (ao->other_replay_gain_filter != NULL) - filter_free(ao->other_replay_gain_filter); - - filter_free(ao->filter); - - pcm_buffer_deinit(&ao->cross_fade_buffer); -} - -void -audio_output_free(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->thread == NULL); - - ao_plugin_finish(ao); -} diff --git a/src/output_init.c b/src/output_init.c deleted file mode 100644 index c3b808e94..000000000 --- a/src/output_init.c +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_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) -{ - g_warning("Attempt to detect audio output device"); - - audio_output_plugins_for_each(plugin) { - if (plugin->test_default_device == NULL) - continue; - - g_warning("Attempting to detect a %s audio device", - plugin->name); - if (ao_plugin_test_default_device(plugin)) - return plugin; - } - - g_set_error(error, audio_output_quark(), 0, - "Unable to detect an audio device"); - 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(struct audio_output *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 -ao_base_init(struct audio_output *ao, - const struct audio_output_plugin *plugin, - const struct config_param *param, GError **error_r) -{ - assert(ao != NULL); - assert(plugin != NULL); - assert(plugin->finish != NULL); - assert(plugin->open != NULL); - assert(plugin->close != NULL); - assert(plugin->play != NULL); - - GError *error = NULL; - - if (param) { - const char *p; - - ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, - NULL); - if (ao->name == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Missing \"name\" configuration"); - return false; - } - - 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 { - ao->name = "default detected output"; - - 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->allow_play = true; - ao->fail_timer = NULL; - - pcm_buffer_init(&ao->cross_fade_buffer); - - /* set up the filter chain */ - - ao->filter = filter_chain_new(); - assert(ao->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; - ao->command = AO_COMMAND_NONE; - ao->mutex = g_mutex_new(); - ao->cond = g_cond_new(); - - ao->mixer = NULL; - ao->replay_gain_filter = NULL; - ao->other_replay_gain_filter = NULL; - - /* done */ - - return true; -} - -static bool -audio_output_setup(struct audio_output *ao, const struct config_param *param, - GError **error_r) -{ - - /* create the replay_gain filter */ - - const char *replay_gain_handler = - config_get_block_string(param, "replay_gain_handler", - "software"); - - if (strcmp(replay_gain_handler, "none") != 0) { - ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, NULL); - assert(ao->replay_gain_filter != NULL); - - ao->replay_gain_serial = 0; - - ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, NULL); - assert(ao->other_replay_gain_filter != NULL); - - ao->other_replay_gain_serial = 0; - } else { - ao->replay_gain_filter = NULL; - ao->other_replay_gain_filter = NULL; - } - - /* set up the mixer */ - - GError *error = NULL; - ao->mixer = audio_output_load_mixer(ao, param, - ao->plugin->mixer_plugin, - ao->filter, &error); - if (ao->mixer == NULL && error != NULL) { - g_warning("Failed to initialize hardware mixer for '%s': %s", - 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); - - return true; -} - -struct audio_output * -audio_output_new(const struct config_param *param, - struct player_control *pc, - GError **error_r) -{ - const struct audio_output_plugin *plugin; - - if (param) { - const char *p; - - p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); - if (p == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Missing \"type\" configuration"); - return false; - } - - plugin = audio_output_plugin_get(p); - if (plugin == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "No such audio output plugin: %s", p); - return false; - } - } else { - g_warning("No \"%s\" defined in config file\n", - CONF_AUDIO_OUTPUT); - - plugin = audio_output_detect(error_r); - if (plugin == NULL) - return false; - - g_message("Successfully detected a %s audio device", - plugin->name); - } - - struct audio_output *ao = ao_plugin_init(plugin, param, error_r); - if (ao == NULL) - return NULL; - - if (!audio_output_setup(ao, param, error_r)) { - ao_plugin_finish(ao); - return NULL; - } - - ao->player_control = pc; - return ao; -} diff --git a/src/output_internal.h b/src/output_internal.h deleted file mode 100644 index 9d975d789..000000000 --- a/src/output_internal.h +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_INTERNAL_H -#define MPD_OUTPUT_INTERNAL_H - -#include "audio_format.h" -#include "pcm_buffer.h" - -#include <glib.h> - -#include <time.h> - -struct config_param; - -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 -}; - -struct audio_output { - /** - * The device's configured display name. - */ - const char *name; - - /** - * The plugin which implements this output device. - */ - const struct audio_output_plugin *plugin; - - /** - * The #mixer object associated with this audio output device. - * May be NULL if none is available, or if software volume is - * configured. - */ - struct mixer *mixer; - - /** - * Shall this output always play something (i.e. silence), - * even when playback is stopped? - */ - bool always_on; - - /** - * Has the user enabled this device? - */ - 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. - * It is protected with #mutex: write accesses inside the - * output thread and read accesses outside of it may only be - * performed while the lock is held. - */ - bool open; - - /** - * Is the device paused? i.e. the output thread is in the - * ao_pause() loop. - */ - bool pause; - - /** - * When this flag is set, the output thread will not do any - * playback. It will wait until the flag is cleared. - * - * This is used to synchronize the "clear" operation on the - * shared music pipe during the CANCEL command. - */ - bool allow_play; - - /** - * If not NULL, the device has failed, and this timer is used - * to estimate how long it should stay disabled (unless - * explicitly reopened with "play"). - */ - 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). - */ - struct audio_format in_audio_format; - - /** - * The audio_format which is really sent to the device. This - * is basically config_audio_format (if configured) or - * in_audio_format, but may have been modified by - * plugin->open(). - */ - struct audio_format out_audio_format; - - /** - * The buffer used to allocate the cross-fading result. - */ - struct pcm_buffer cross_fade_buffer; - - /** - * The filter object of this audio output. This is an - * instance of chain_filter_plugin. - */ - struct filter *filter; - - /** - * The replay_gain_filter_plugin instance of this audio - * output. - */ - 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. - */ - enum audio_output_command command; - - /** - * The music pipe which provides music chunks to be played. - */ - const struct music_pipe *pipe; - - /** - * This mutex protects #open, #fail_timer, #chunk and - * #chunk_finished. - */ - GMutex *mutex; - - /** - * This condition object wakes up the output thread after - * #command has been set. - */ - GCond *cond; - - /** - * The player_control object which "owns" this output. This - * object is needed to signal command completion. - */ - struct player_control *player_control; - - /** - * The #music_chunk which is currently being played. All - * chunks before this one may be returned to the - * #music_buffer, because they are not going to be used by - * this output anymore. - */ - const struct music_chunk *chunk; - - /** - * Has the output finished playing #chunk? - */ - bool chunk_finished; -}; - -/** - * Notify object used by the thread's client, i.e. we will send a - * notify signal to this object, expecting the caller to wait on it. - */ -extern struct notify audio_output_client_notify; - -static inline bool -audio_output_is_open(const struct audio_output *ao) -{ - return ao->open; -} - -static inline bool -audio_output_command_is_finished(const struct audio_output *ao) -{ - return ao->command == AO_COMMAND_NONE; -} - -struct audio_output * -audio_output_new(const struct config_param *param, - struct player_control *pc, - GError **error_r); - -bool -ao_base_init(struct audio_output *ao, - const struct audio_output_plugin *plugin, - const struct config_param *param, GError **error_r); - -void -ao_base_finish(struct audio_output *ao); - -void -audio_output_free(struct audio_output *ao); - -#endif diff --git a/src/output_list.c b/src/output_list.c deleted file mode 100644 index 835c02bba..000000000 --- a/src/output_list.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_list.h" -#include "output_api.h" -#include "output/alsa_output_plugin.h" -#include "output/ao_output_plugin.h" -#include "output/ffado_output_plugin.h" -#include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" -#include "output/jack_output_plugin.h" -#include "output/mvp_output_plugin.h" -#include "output/null_output_plugin.h" -#include "output/openal_output_plugin.h" -#include "output/oss_output_plugin.h" -#include "output/osx_output_plugin.h" -#include "output/pipe_output_plugin.h" -#include "output/pulse_output_plugin.h" -#include "output/recorder_output_plugin.h" -#include "output/roar_output_plugin.h" -#include "output/shout_output_plugin.h" -#include "output/solaris_output_plugin.h" -#include "output/winmm_output_plugin.h" - -const struct audio_output_plugin *const audio_output_plugins[] = { -#ifdef HAVE_SHOUT - &shout_output_plugin, -#endif - &null_output_plugin, -#ifdef HAVE_FIFO - &fifo_output_plugin, -#endif -#ifdef ENABLE_PIPE_OUTPUT - &pipe_output_plugin, -#endif -#ifdef HAVE_ALSA - &alsa_output_plugin, -#endif -#ifdef HAVE_ROAR - &roar_output_plugin, -#endif -#ifdef HAVE_AO - &ao_output_plugin, -#endif -#ifdef HAVE_OSS - &oss_output_plugin, -#endif -#ifdef HAVE_OPENAL - &openal_output_plugin, -#endif -#ifdef HAVE_OSX - &osx_output_plugin, -#endif -#ifdef ENABLE_SOLARIS_OUTPUT - &solaris_output_plugin, -#endif -#ifdef HAVE_PULSE - &pulse_output_plugin, -#endif -#ifdef HAVE_MVP - &mvp_output_plugin, -#endif -#ifdef HAVE_JACK - &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 -}; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name) -{ - audio_output_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} diff --git a/src/output_list.h b/src/output_list.h deleted file mode 100644 index 185ada716..000000000 --- a/src/output_list.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_LIST_H -#define MPD_OUTPUT_LIST_H - -extern const struct audio_output_plugin *const audio_output_plugins[]; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name); - -#define audio_output_plugins_for_each(plugin) \ - for (const struct audio_output_plugin *plugin, \ - *const*output_plugin_iterator = &audio_output_plugins[0]; \ - (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator) - -#endif diff --git a/src/output_plugin.c b/src/output_plugin.c deleted file mode 100644 index 221570c1c..000000000 --- a/src/output_plugin.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_plugin.h" -#include "output_internal.h" - -struct audio_output * -ao_plugin_init(const struct audio_output_plugin *plugin, - const struct config_param *param, - GError **error) -{ - assert(plugin != NULL); - assert(plugin->init != NULL); - - return plugin->init(param, error); -} - -void -ao_plugin_finish(struct audio_output *ao) -{ - ao->plugin->finish(ao); -} - -bool -ao_plugin_enable(struct audio_output *ao, GError **error_r) -{ - return ao->plugin->enable != NULL - ? ao->plugin->enable(ao, error_r) - : true; -} - -void -ao_plugin_disable(struct audio_output *ao) -{ - if (ao->plugin->disable != NULL) - ao->plugin->disable(ao); -} - -bool -ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - return ao->plugin->open(ao, audio_format, error); -} - -void -ao_plugin_close(struct audio_output *ao) -{ - ao->plugin->close(ao); -} - -unsigned -ao_plugin_delay(struct audio_output *ao) -{ - return ao->plugin->delay != NULL - ? ao->plugin->delay(ao) - : 0; -} - -void -ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag) -{ - if (ao->plugin->send_tag != NULL) - ao->plugin->send_tag(ao, tag); -} - -size_t -ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - return ao->plugin->play(ao, chunk, size, error); -} - -void -ao_plugin_drain(struct audio_output *ao) -{ - if (ao->plugin->drain != NULL) - ao->plugin->drain(ao); -} - -void -ao_plugin_cancel(struct audio_output *ao) -{ - if (ao->plugin->cancel != NULL) - ao->plugin->cancel(ao); -} - -bool -ao_plugin_pause(struct audio_output *ao) -{ - return ao->plugin->pause != NULL && ao->plugin->pause(ao); -} diff --git a/src/output_plugin.h b/src/output_plugin.h deleted file mode 100644 index 209ca6221..000000000 --- a/src/output_plugin.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_PLUGIN_H -#define MPD_OUTPUT_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> -#include <stddef.h> - -struct config_param; -struct audio_format; -struct tag; - -/** - * A plugin which controls an audio output device. - */ -struct audio_output_plugin { - /** - * the plugin's name - */ - const char *name; - - /** - * Test if this plugin can provide a default output, in case - * none has been configured. This method is optional. - */ - bool (*test_default_device)(void); - - /** - * Configure and initialize the device, but do not open it - * yet. - * - * @param param the configuration section, or NULL if there is - * no configuration - * @param error location to store the error occurring, or NULL - * to ignore errors - * @return NULL on error, or an opaque pointer to the plugin's - * data - */ - struct audio_output *(*init)(const struct config_param *param, - GError **error); - - /** - * Free resources allocated by this device. - */ - void (*finish)(struct audio_output *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 occurring, or - * NULL to ignore errors - * @return true on success, false on error - */ - bool (*enable)(struct audio_output *data, GError **error_r); - - /** - * Disables the device. It is closed before this method is - * called. - */ - void (*disable)(struct audio_output *data); - - /** - * Really open the device. - * - * @param audio_format the audio format in which data is going - * to be delivered; may be modified by the plugin - * @param error location to store the error occurring, or NULL - * to ignore errors - */ - bool (*open)(struct audio_output *data, struct audio_format *audio_format, - GError **error); - - /** - * Close the device. - */ - void (*close)(struct audio_output *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)(struct audio_output *data); - - /** - * Display metadata for the next chunk. Optional method, - * because not all devices can display metadata. - */ - void (*send_tag)(struct audio_output *data, const struct tag *tag); - - /** - * Play a chunk of audio data. - * - * @param error location to store the error occurring, or NULL - * to ignore errors - * @return the number of bytes played, or 0 on error - */ - size_t (*play)(struct audio_output *data, - const void *chunk, size_t size, - GError **error); - - /** - * Wait until the device has finished playing. - */ - void (*drain)(struct audio_output *data); - - /** - * Try to cancel data which may still be in the device's - * buffers. - */ - void (*cancel)(struct audio_output *data); - - /** - * Pause the device. If supported, it may perform a special - * action, which keeps the device open, but does not play - * anything. Output plugins like "shout" might want to play - * silence during pause, so their clients won't be - * disconnected. Plugins which do not support pausing will - * simply be closed, and have to be reopened when unpaused. - * - * @return false on error (output will be closed then), true - * for continue to pause - */ - bool (*pause)(struct audio_output *data); - - /** - * The mixer plugin associated with this output plugin. This - * may be NULL if no mixer plugin is implemented. When - * created, this mixer plugin gets the same #config_param as - * this audio output device. - */ - const struct mixer_plugin *mixer_plugin; -}; - -static inline bool -ao_plugin_test_default_device(const struct audio_output_plugin *plugin) -{ - return plugin->test_default_device != NULL - ? plugin->test_default_device() - : false; -} - -G_GNUC_MALLOC -struct audio_output * -ao_plugin_init(const struct audio_output_plugin *plugin, - const struct config_param *param, - GError **error); - -void -ao_plugin_finish(struct audio_output *ao); - -bool -ao_plugin_enable(struct audio_output *ao, GError **error_r); - -void -ao_plugin_disable(struct audio_output *ao); - -bool -ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error); - -void -ao_plugin_close(struct audio_output *ao); - -G_GNUC_PURE -unsigned -ao_plugin_delay(struct audio_output *ao); - -void -ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag); - -size_t -ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error); - -void -ao_plugin_drain(struct audio_output *ao); - -void -ao_plugin_cancel(struct audio_output *ao); - -bool -ao_plugin_pause(struct audio_output *ao); - -#endif diff --git a/src/output_print.c b/src/output_print.c deleted file mode 100644 index 483648ca2..000000000 --- a/src/output_print.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Protocol specific code for the audio output library. - * - */ - -#include "config.h" -#include "output_print.h" -#include "output_internal.h" -#include "output_all.h" -#include "client.h" - -void -printAudioDevices(struct client *client) -{ - unsigned n = audio_output_count(); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); - - client_printf(client, - "outputid: %i\n" - "outputname: %s\n" - "outputenabled: %i\n", - i, ao->name, ao->enabled); - } -} diff --git a/src/output_print.h b/src/output_print.h deleted file mode 100644 index e02f4e9f5..000000000 --- a/src/output_print.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Protocol specific code for the audio output library. - * - */ - -#ifndef OUTPUT_PRINT_H -#define OUTPUT_PRINT_H - -struct client; - -void -printAudioDevices(struct client *client); - -#endif diff --git a/src/output_state.c b/src/output_state.c deleted file mode 100644 index 7bcafb36b..000000000 --- a/src/output_state.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the audio output states to/from the state file. - * - */ - -#include "config.h" -#include "output_state.h" -#include "output_internal.h" -#include "output_all.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#define AUDIO_DEVICE_STATE "audio_device_state:" - -unsigned audio_output_state_version; - -void -audio_output_state_save(FILE *fp) -{ - unsigned n = audio_output_count(); - - assert(n > 0); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); - - fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", - ao->enabled, ao->name); - } -} - -bool -audio_output_state_read(const char *line) -{ - long value; - char *endptr; - const char *name; - struct audio_output *ao; - - if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) - return false; - - line += sizeof(AUDIO_DEVICE_STATE) - 1; - - value = strtol(line, &endptr, 10); - if (*endptr != ':' || (value != 0 && value != 1)) - return false; - - if (value != 0) - /* state is "enabled": no-op */ - return true; - - name = endptr + 1; - ao = audio_output_find(name); - if (ao == NULL) { - g_debug("Ignoring device state for '%s'", name); - return true; - } - - ao->enabled = false; - return true; -} - -unsigned -audio_output_state_get_version(void) -{ - return audio_output_state_version; -} diff --git a/src/output_state.h b/src/output_state.h deleted file mode 100644 index 320a3520a..000000000 --- a/src/output_state.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the audio output states to/from the state file. - * - */ - -#ifndef OUTPUT_STATE_H -#define OUTPUT_STATE_H - -#include <stdbool.h> -#include <stdio.h> - -bool -audio_output_state_read(const char *line); - -void -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 deleted file mode 100644 index 4eef2ccdd..000000000 --- a/src/output_thread.c +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_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 "notify.h" -#include "gcc.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -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, &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); - 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, &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, bool drain) -{ - assert(ao->open); - - ao->pipe = NULL; - - ao->chunk = NULL; - ao->open = false; - - g_mutex_unlock(ao->mutex); - - if (drain) - ao_plugin_drain(ao); - else - ao_plugin_cancel(ao); - - ao_plugin_close(ao); - ao_filter_close(ao); - - g_mutex_lock(ao->mutex); - - g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); -} - -static void -ao_reopen_filter(struct audio_output *ao) -{ - 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); - 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); - 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)); - - 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); - if (!pcm_mix(dest, data, length, ao->in_audio_format.format, - 1.0 - chunk->mix_ratio)) { - g_warning("Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); - return NULL; - } - - data = dest; - length = other_length; - } - - /* 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, chunk->tag); - g_mutex_lock(ao->mutex); - } - - size_t size; -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - size = 0; -#endif - const char *data = ao_filter_chunk(ao, chunk, &size); - if (data == NULL) { - ao_close(ao, false); - - /* 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, 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_close(ao, false); - - /* don't automatically reopen this device for - 10 seconds */ - assert(ao->fail_timer == NULL); - ao->fail_timer = g_timer_new(); - - return false; - } - - assert(nbytes <= size); - assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0); - - data += nbytes; - size -= nbytes; - } - - return true; -} - -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); - - 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; - - success = ao_play_chunk(ao, chunk); - if (!success) { - assert(ao->chunk == NULL); - break; - } - - assert(ao->chunk == chunk); - chunk = chunk->next; - } - - ao->chunk_finished = true; - - g_mutex_unlock(ao->mutex); - player_lock_signal(ao->player_control); - g_mutex_lock(ao->mutex); - - return true; -} - -static void ao_pause(struct audio_output *ao) -{ - bool ret; - - g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao); - 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); - g_mutex_lock(ao->mutex); - - if (!ret) { - ao_close(ao, false); - break; - } - } while (ao->command == AO_COMMAND_NONE); - - ao->pause = false; -} - -static gpointer audio_output_task(gpointer arg) -{ - struct audio_output *ao = arg; - - g_mutex_lock(ao->mutex); - - while (1) { - switch (ao->command) { - case AO_COMMAND_NONE: - break; - - case AO_COMMAND_ENABLE: - ao_enable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_DISABLE: - ao_disable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_OPEN: - ao_open(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_REOPEN: - ao_reopen(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_CLOSE: - assert(ao->open); - assert(ao->pipe != NULL); - - ao_close(ao, false); - ao_command_finished(ao); - break; - - case AO_COMMAND_PAUSE: - if (!ao->open) { - /* the output has failed after - audio_output_all_pause() has - submitted the PAUSE command; bail - out */ - ao_command_finished(ao); - break; - } - - ao_pause(ao); - /* don't "break" here: this might cause - ao_play() to be called when command==CLOSE - ends the paused state - "continue" checks - 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); - g_mutex_lock(ao->mutex); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_CANCEL: - ao->chunk = NULL; - - if (ao->open) { - g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao); - g_mutex_lock(ao->mutex); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_KILL: - ao->chunk = NULL; - ao_command_finished(ao); - g_mutex_unlock(ao->mutex); - return NULL; - } - - if (ao->open && ao->allow_play && ao_play(ao)) - /* don't wait for an event if there are more - chunks in the pipe */ - continue; - - if (ao->command == AO_COMMAND_NONE) - g_cond_wait(ao->cond, ao->mutex); - } -} - -void audio_output_thread_start(struct audio_output *ao) -{ - GError *e = NULL; - - assert(ao->command == AO_COMMAND_NONE); - - if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) - MPD_ERROR("Failed to spawn output task: %s\n", e->message); -} diff --git a/src/output_thread.h b/src/output_thread.h deleted file mode 100644 index 5ad9a7527..000000000 --- a/src/output_thread.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_THREAD_H -#define MPD_OUTPUT_THREAD_H - -struct audio_output; - -void audio_output_thread_start(struct audio_output *ao); - -#endif diff --git a/src/page.c b/src/page.c deleted file mode 100644 index e2e22791f..000000000 --- a/src/page.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "page.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -/** - * Allocates a new #page object, without filling the data element. - */ -static struct page * -page_new(size_t size) -{ - struct page *page = g_malloc(sizeof(*page) + size - - sizeof(page->data)); - - assert(size > 0); - - page->ref = 1; - page->size = size; - return page; -} - -struct page * -page_new_copy(const void *data, size_t size) -{ - struct page *page = page_new(size); - - assert(data != NULL); - - memcpy(page->data, data, size); - return page; -} - -struct page * -page_new_concat(const struct page *a, const struct page *b) -{ - struct page *page = page_new(a->size + b->size); - - memcpy(page->data, a->data, a->size); - memcpy(page->data + a->size, b->data, b->size); - - return page; -} - -void -page_ref(struct page *page) -{ - g_atomic_int_inc(&page->ref); -} - -static void -page_free(struct page *page) -{ - assert(page->ref == 0); - - g_free(page); -} - -bool -page_unref(struct page *page) -{ - bool unused = g_atomic_int_dec_and_test(&page->ref); - - if (unused) - page_free(page); - - return unused; -} diff --git a/src/page.h b/src/page.h deleted file mode 100644 index 8a3aaf396..000000000 --- a/src/page.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This is a library which manages reference counted buffers. - */ - -#ifndef MPD_PAGE_H -#define MPD_PAGE_H - -#include <stddef.h> -#include <stdbool.h> - -/** - * A dynamically allocated buffer which keeps track of its reference - * count. This is useful for passing buffers around, when several - * instances hold references to one buffer. - */ -struct page { - /** - * The number of references to this buffer. This library uses - * atomic functions to access it, i.e. no locks are required. - * As soon as this attribute reaches zero, the buffer is - * freed. - */ - int ref; - - /** - * The size of this buffer in bytes. - */ - size_t size; - - /** - * Dynamic array containing the buffer data. - */ - unsigned char data[sizeof(long)]; -}; - -/** - * Creates a new #page object, and copies data from the specified - * buffer. It is initialized with a reference count of 1. - * - * @param data the source buffer - * @param size the size of the source buffer - * @return the new #page object - */ -struct page * -page_new_copy(const void *data, size_t size); - -/** - * Concatenates two pages to a new page. - * - * @param a the first page - * @param b the second page, which is appended - */ -struct page * -page_new_concat(const struct page *a, const struct page *b); - -/** - * Increases the reference counter. - * - * @param page the #page object - */ -void -page_ref(struct page *page); - -/** - * Decreases the reference counter. If it reaches zero, the #page is - * freed. - * - * @param page the #page object - * @return true if the #page has been freed - */ -bool -page_unref(struct page *page); - -#endif diff --git a/src/path.c b/src/path.c deleted file mode 100644 index 59a91a0f7..000000000 --- a/src/path.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "path.h" -#include "conf.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#ifdef G_OS_WIN32 -#include <windows.h> // for GetACP() -#include <stdio.h> // for sprintf() -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "path" - -static char *fs_charset; - -char * -fs_charset_to_utf8(const char *path_fs) -{ - return g_convert(path_fs, -1, - "utf-8", fs_charset, - NULL, NULL, NULL); -} - -char * -utf8_to_fs_charset(const char *path_utf8) -{ - gchar *p; - - p = g_convert(path_utf8, -1, - fs_charset, "utf-8", - NULL, NULL, NULL); - if (p == NULL) - /* fall back to UTF-8 */ - p = g_strdup(path_utf8); - - return p; -} - -static void -path_set_fs_charset(const char *charset) -{ - char *test; - - assert(charset != NULL); - - /* convert a space to ensure that the charset is valid */ - test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); - if (test == NULL) - MPD_ERROR("invalid filesystem charset: %s", charset); - g_free(test); - - g_free(fs_charset); - fs_charset = g_strdup(charset); - - g_debug("path_set_fs_charset: fs charset is: %s", fs_charset); -} - -const char *path_get_fs_charset(void) -{ - return fs_charset; -} - -void path_global_init(void) -{ - const char *charset = NULL; - - charset = config_get_string(CONF_FS_CHARSET, NULL); - if (charset == NULL) { -#ifndef G_OS_WIN32 - const gchar **encodings; - g_get_filename_charsets(&encodings); - - if (encodings[0] != NULL && *encodings[0] != '\0') - charset = encodings[0]; -#else /* G_OS_WIN32 */ - /* Glib claims that file system encoding is always utf-8 - * on native Win32 (i.e. not Cygwin). - * However this is true only if <gstdio.h> helpers are used. - * MPD uses regular <stdio.h> functions. - * Those functions use encoding determined by GetACP(). */ - char win_charset[13]; - sprintf(win_charset, "cp%u", GetACP()); - charset = win_charset; -#endif - } - - if (charset) { - path_set_fs_charset(charset); - } else { - g_message("setting filesystem charset to ISO-8859-1"); - path_set_fs_charset("ISO-8859-1"); - } -} - -void path_global_finish(void) -{ - g_free(fs_charset); -} diff --git a/src/path.h b/src/path.h deleted file mode 100644 index 00c368e70..000000000 --- a/src/path.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PATH_H -#define MPD_PATH_H - -#include <limits.h> - -#if !defined(MPD_PATH_MAX) -# if defined(MAXPATHLEN) -# define MPD_PATH_MAX MAXPATHLEN -# elif defined(PATH_MAX) -# define MPD_PATH_MAX PATH_MAX -# else -# define MPD_PATH_MAX 256 -# endif -#endif - -void path_global_init(void); - -void path_global_finish(void); - -/** - * Converts a file name in the filesystem charset to UTF-8. Returns - * NULL on failure. - */ -char * -fs_charset_to_utf8(const char *path_fs); - -/** - * Converts a file name in UTF-8 to the filesystem charset. Returns a - * duplicate of the UTF-8 string on failure. - */ -char * -utf8_to_fs_charset(const char *path_utf8); - -const char *path_get_fs_charset(void); - -#endif diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx new file mode 100644 index 000000000..6ace399f3 --- /dev/null +++ b/src/pcm/PcmBuffer.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmBuffer.hxx" +#include "poison.h" + +void * +PcmBuffer::Get(size_t new_size) +{ + if (new_size == 0) + /* never return NULL, because NULL would be assumed to + be an error condition */ + new_size = 1; + + return buffer.Get(new_size); +} diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx new file mode 100644 index 000000000..2eddfb7f9 --- /dev/null +++ b/src/pcm/PcmBuffer.hxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PCM_BUFFER_HXX +#define PCM_BUFFER_HXX + +#include "util/ReusableArray.hxx" +#include "gcc.h" + +#include <stdint.h> + +/** + * Manager for a temporary buffer which grows as needed. We could + * allocate a new buffer every time pcm_convert() is called, but that + * would put too much stress on the allocator. + */ +class PcmBuffer { + ReusableArray<uint8_t, 8192> buffer; + +public: + void Clear() { + buffer.Clear(); + } + + /** + * Get the buffer, and guarantee a minimum size. This buffer becomes + * invalid with the next pcm_buffer_get() call. + * + * This function will never return NULL, even if size is zero, because + * the PCM library uses the NULL return value to signal "error". An + * empty destination buffer is not always an error. + */ + gcc_malloc + void *Get(size_t size); +}; + +#endif diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx new file mode 100644 index 000000000..27a155063 --- /dev/null +++ b/src/pcm/PcmChannels.cxx @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmChannels.hxx" +#include "PcmBuffer.hxx" +#include "PcmUtils.hxx" + +#include <assert.h> + +template<typename D, typename S> +static void +MonoToStereo(D dest, S src, S end) +{ + while (src != end) { + const auto value = *src++; + + *dest++ = value; + *dest++ = value; + } + +} + +static void +pcm_convert_channels_16_2_to_1(int16_t *restrict dest, + const int16_t *restrict src, + const int16_t *restrict src_end) +{ + while (src < src_end) { + int32_t a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_16_n_to_2(int16_t *restrict dest, + unsigned src_channels, + const int16_t *restrict src, + const int16_t *restrict src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + int32_t sum = 0; + int16_t value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (int)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const int16_t * +pcm_convert_channels_16(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const int16_t *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + int16_t *dest = (int16_t *)buffer.Get(dest_size); + const int16_t *src_end = pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_16_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_16_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} + +static void +pcm_convert_channels_24_2_to_1(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) +{ + while (src < src_end) { + int32_t a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_24_n_to_2(int32_t *restrict dest, + unsigned src_channels, + const int32_t *restrict src, + const int32_t *restrict src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + int32_t sum = 0; + int32_t value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (int)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const int32_t * +pcm_convert_channels_24(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + int32_t *dest = (int32_t *)buffer.Get(dest_size); + const int32_t *src_end = (const int32_t *) + pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_24_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_24_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} + +static void +pcm_convert_channels_32_2_to_1(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) +{ + while (src < src_end) { + int64_t a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_32_n_to_2(int32_t *dest, + unsigned src_channels, const int32_t *src, + const int32_t *src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + int64_t sum = 0; + int32_t value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (int64_t)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const int32_t * +pcm_convert_channels_32(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + int32_t *dest = (int32_t *)buffer.Get(dest_size); + const int32_t *src_end = (const int32_t *) + pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_32_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_32_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} + +static void +pcm_convert_channels_float_2_to_1(float *restrict dest, + const float *restrict src, + const float *restrict src_end) +{ + while (src < src_end) { + double a = *src++, b = *src++; + + *dest++ = (a + b) / 2; + } +} + +static void +pcm_convert_channels_float_n_to_2(float *dest, + unsigned src_channels, const float *src, + const float *src_end) +{ + unsigned c; + + assert(src_channels > 0); + + while (src < src_end) { + double sum = 0; + float value; + + for (c = 0; c < src_channels; ++c) + sum += *src++; + value = sum / (double)src_channels; + + /* XXX this is actually only mono ... */ + *dest++ = value; + *dest++ = value; + } +} + +const float * +pcm_convert_channels_float(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % (sizeof(*src) * src_channels) == 0); + + size_t dest_size = src_size / src_channels * dest_channels; + *dest_size_r = dest_size; + + float *dest = (float *)buffer.Get(dest_size); + const float *src_end = (const float *)pcm_end_pointer(src, src_size); + + if (src_channels == 1 && dest_channels == 2) + MonoToStereo(dest, src, src_end); + else if (src_channels == 2 && dest_channels == 1) + pcm_convert_channels_float_2_to_1(dest, src, src_end); + else if (dest_channels == 2) + pcm_convert_channels_float_n_to_2(dest, src_channels, src, + src_end); + else + return NULL; + + return dest; +} diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx new file mode 100644 index 000000000..c67822825 --- /dev/null +++ b/src/pcm/PcmChannels.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CHANNELS_HXX +#define MPD_PCM_CHANNELS_HXX + +#include <stdint.h> +#include <stddef.h> + +class PcmBuffer; + +/** + * Changes the number of channels in 16 bit PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int16_t * +pcm_convert_channels_16(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const int16_t *src, + size_t src_size, size_t *dest_size_r); + +/** + * Changes the number of channels in 24 bit PCM data (aligned at 32 + * bit boundaries). + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_channels_24(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r); + +/** + * Changes the number of channels in 32 bit PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_channels_32(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, + size_t src_size, size_t *dest_size_r); + +/** + * Changes the number of channels in 32 bit float PCM data. + * + * @param buffer the destination pcm_buffer object + * @param dest_channels the number of channels requested + * @param src_channels the number of channels in the source buffer + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_channels_float(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, const float *src, + size_t src_size, size_t *dest_size_r); + +#endif diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx new file mode 100644 index 000000000..4260ccb0f --- /dev/null +++ b/src/pcm/PcmConvert.cxx @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmConvert.hxx" +#include "PcmChannels.hxx" +#include "PcmFormat.hxx" +#include "pcm_pack.h" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <assert.h> +#include <math.h> + +const Domain pcm_convert_domain("pcm_convert"); + +PcmConvert::PcmConvert() +{ +} + +PcmConvert::~PcmConvert() +{ +} + +void +PcmConvert::Reset() +{ + dsd.Reset(); + resampler.Reset(); +} + +inline const int16_t * +PcmConvert::Convert16(const AudioFormat src_format, + const void *src_buffer, size_t src_size, + const AudioFormat dest_format, size_t *dest_size_r, + Error &error) +{ + const int16_t *buf; + size_t len; + + assert(dest_format.format == SampleFormat::S16); + + buf = pcm_convert_to_16(format_buffer, dither, + src_format.format, + src_buffer, src_size, + &len); + if (buf == NULL) { + error.Format(pcm_convert_domain, + "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(channels_buffer, + dest_format.channels, + src_format.channels, + buf, len, &len); + if (buf == NULL) { + error.Format(pcm_convert_domain, + "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) { + buf = resampler.Resample16(dest_format.channels, + src_format.sample_rate, buf, len, + dest_format.sample_rate, &len, + error); + if (buf == NULL) + return NULL; + } + + *dest_size_r = len; + return buf; +} + +inline const int32_t * +PcmConvert::Convert24(const AudioFormat src_format, + const void *src_buffer, size_t src_size, + const AudioFormat dest_format, size_t *dest_size_r, + Error &error) +{ + const int32_t *buf; + size_t len; + + assert(dest_format.format == SampleFormat::S24_P32); + + buf = pcm_convert_to_24(format_buffer, + src_format.format, + src_buffer, src_size, &len); + if (buf == NULL) { + error.Format(pcm_convert_domain, + "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(channels_buffer, + dest_format.channels, + src_format.channels, + buf, len, &len); + if (buf == NULL) { + error.Format(pcm_convert_domain, + "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) { + buf = resampler.Resample24(dest_format.channels, + src_format.sample_rate, buf, len, + dest_format.sample_rate, &len, + error); + if (buf == NULL) + return NULL; + } + + *dest_size_r = len; + return buf; +} + +inline const int32_t * +PcmConvert::Convert32(const AudioFormat src_format, + const void *src_buffer, size_t src_size, + const AudioFormat dest_format, size_t *dest_size_r, + Error &error) +{ + const int32_t *buf; + size_t len; + + assert(dest_format.format == SampleFormat::S32); + + buf = pcm_convert_to_32(format_buffer, + src_format.format, + src_buffer, src_size, &len); + if (buf == NULL) { + error.Format(pcm_convert_domain, + "Conversion from %s to 32 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(channels_buffer, + dest_format.channels, + src_format.channels, + buf, len, &len); + if (buf == NULL) { + error.Format(pcm_convert_domain, + "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) { + buf = resampler.Resample32(dest_format.channels, + src_format.sample_rate, buf, len, + dest_format.sample_rate, &len, + error); + if (buf == NULL) + return buf; + } + + *dest_size_r = len; + return buf; +} + +inline const float * +PcmConvert::ConvertFloat(const AudioFormat src_format, + const void *src_buffer, size_t src_size, + const AudioFormat dest_format, size_t *dest_size_r, + Error &error) +{ + const float *buffer = (const float *)src_buffer; + size_t size = src_size; + + assert(dest_format.format == SampleFormat::FLOAT); + + /* convert to float now */ + + buffer = pcm_convert_to_float(format_buffer, + src_format.format, + buffer, size, &size); + if (buffer == NULL) { + error.Format(pcm_convert_domain, + "Conversion from %s to float is not implemented", + sample_format_to_string(src_format.format)); + return NULL; + } + + /* convert channels */ + + if (src_format.channels != dest_format.channels) { + buffer = pcm_convert_channels_float(channels_buffer, + dest_format.channels, + src_format.channels, + buffer, size, &size); + if (buffer == NULL) { + error.Format(pcm_convert_domain, + "Conversion from %u to %u channels " + "is not implemented", + src_format.channels, + dest_format.channels); + return NULL; + } + } + + /* resample with float, because this is the best format for + libsamplerate */ + + if (src_format.sample_rate != dest_format.sample_rate) { + buffer = resampler.ResampleFloat(dest_format.channels, + src_format.sample_rate, + buffer, size, + dest_format.sample_rate, + &size, error); + if (buffer == NULL) + return NULL; + } + + *dest_size_r = size; + return buffer; +} + +const void * +PcmConvert::Convert(AudioFormat src_format, + const void *src, size_t src_size, + const AudioFormat dest_format, + size_t *dest_size_r, + Error &error) +{ + AudioFormat float_format; + if (src_format.format == SampleFormat::DSD) { + size_t f_size; + const float *f = dsd.ToFloat(src_format.channels, + false, (const uint8_t *)src, + src_size, &f_size); + if (f == NULL) { + error.Set(pcm_convert_domain, + "DSD to PCM conversion failed"); + return NULL; + } + + float_format = src_format; + float_format.format = SampleFormat::FLOAT; + + src_format = float_format; + src = f; + src_size = f_size; + } + + switch (dest_format.format) { + case SampleFormat::S16: + return Convert16(src_format, src, src_size, + dest_format, dest_size_r, + error); + + case SampleFormat::S24_P32: + return Convert24(src_format, src, src_size, + dest_format, dest_size_r, + error); + + case SampleFormat::S32: + return Convert32(src_format, src, src_size, + dest_format, dest_size_r, + error); + + case SampleFormat::FLOAT: + return ConvertFloat(src_format, src, src_size, + dest_format, dest_size_r, + error); + + default: + error.Format(pcm_convert_domain, + "PCM conversion to %s is not implemented", + sample_format_to_string(dest_format.format)); + return NULL; + } +} diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx new file mode 100644 index 000000000..40f785179 --- /dev/null +++ b/src/pcm/PcmConvert.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PCM_CONVERT_HXX +#define PCM_CONVERT_HXX + +#include "PcmDither.hxx" +#include "PcmDsd.hxx" +#include "PcmResample.hxx" +#include "PcmBuffer.hxx" + +#include <stddef.h> + +struct AudioFormat; +class Error; + +/** + * This object is statically allocated (within another struct), and + * holds buffer allocations and the state for all kinds of PCM + * conversions. + */ +class PcmConvert { + PcmDsd dsd; + + PcmResampler resampler; + + PcmDither dither; + + /** the buffer for converting the sample format */ + PcmBuffer format_buffer; + + /** the buffer for converting the channel count */ + PcmBuffer channels_buffer; + +public: + PcmConvert(); + ~PcmConvert(); + + + /** + * Reset the pcm_convert_state object. Use this at the + * boundary between two distinct songs and each time the + * format changes. + */ + void Reset(); + + /** + * Converts PCM data between two audio formats. + * + * @param src_format the source audio format + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_format the requested destination audio format + * @param dest_size_r returns the number of bytes of the destination buffer + * @param error_r location to store the error occurring, or NULL to + * ignore errors + * @return the destination buffer, or NULL on error + */ + const void *Convert(AudioFormat src_format, + const void *src, size_t src_size, + AudioFormat dest_format, + size_t *dest_size_r, + Error &error); + +private: + const int16_t *Convert16(AudioFormat src_format, + const void *src_buffer, size_t src_size, + AudioFormat dest_format, + size_t *dest_size_r, + Error &error); + + const int32_t *Convert24(AudioFormat src_format, + const void *src_buffer, size_t src_size, + AudioFormat dest_format, + size_t *dest_size_r, + Error &error); + + const int32_t *Convert32(AudioFormat src_format, + const void *src_buffer, size_t src_size, + AudioFormat dest_format, + size_t *dest_size_r, + Error &error); + + const float *ConvertFloat(AudioFormat src_format, + const void *src_buffer, size_t src_size, + AudioFormat dest_format, + size_t *dest_size_r, + Error &error); +}; + +extern const class Domain pcm_convert_domain; + +#endif diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx new file mode 100644 index 000000000..98d0d443e --- /dev/null +++ b/src/pcm/PcmDither.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmDither.hxx" +#include "PcmPrng.hxx" + +inline int16_t +PcmDither::Dither24To16(int_fast32_t sample) +{ + constexpr unsigned from_bits = 24; + constexpr unsigned to_bits = 16; + constexpr unsigned scale_bits = from_bits - to_bits; + constexpr int_fast32_t round = 1 << (scale_bits - 1); + constexpr int_fast32_t mask = (1 << scale_bits) - 1; + constexpr int_fast32_t ONE = 1 << (from_bits - 1); + constexpr int_fast32_t MIN = -ONE; + constexpr int_fast32_t MAX = ONE - 1; + + sample += error[0] - error[1] + error[2]; + + error[2] = error[1]; + error[1] = error[0] / 2; + + /* round */ + int_fast32_t output = sample + round; + + int_fast32_t rnd = pcm_prng(random); + output += (rnd & mask) - (random & mask); + + random = rnd; + + /* clip */ + if (output > MAX) { + output = MAX; + + if (sample > MAX) + sample = MAX; + } else if (output < MIN) { + output = MIN; + + if (sample < MIN) + sample = MIN; + } + + output &= ~mask; + + error[0] = sample - output; + + return (int16_t)(output >> scale_bits); +} + +void +PcmDither::Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end) +{ + while (src < src_end) + *dest++ = Dither24To16(*src++); +} + +inline int16_t +PcmDither::Dither32To16(int_fast32_t sample) +{ + return Dither24To16(sample >> 8); +} + +void +PcmDither::Dither32To16(int16_t *dest, const int32_t *src, + const int32_t *src_end) +{ + while (src < src_end) + *dest++ = Dither32To16(*src++); +} diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx new file mode 100644 index 000000000..106382307 --- /dev/null +++ b/src/pcm/PcmDither.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DITHER_HXX +#define MPD_PCM_DITHER_HXX + +#include <stdint.h> + +class PcmDither { + int32_t error[3]; + int32_t random; + +public: + constexpr PcmDither() + :error{0, 0, 0}, random(0) {} + + void Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end); + + void Dither32To16(int16_t *dest, const int32_t *src, + const int32_t *src_end); + +private: + int16_t Dither24To16(int_fast32_t sample); + int16_t Dither32To16(int_fast32_t sample); +}; + +#endif diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx new file mode 100644 index 000000000..096c5464a --- /dev/null +++ b/src/pcm/PcmDsd.cxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmDsd.hxx" +#include "dsd2pcm/dsd2pcm.h" + +#include <glib.h> + +#include <algorithm> + +#include <assert.h> +#include <string.h> + +PcmDsd::PcmDsd() +{ + std::fill_n(dsd2pcm, G_N_ELEMENTS(dsd2pcm), nullptr); +} + +PcmDsd::~PcmDsd() +{ + for (unsigned i = 0; i < G_N_ELEMENTS(dsd2pcm); ++i) + if (dsd2pcm[i] != nullptr) + dsd2pcm_destroy(dsd2pcm[i]); +} + +void +PcmDsd::Reset() +{ + for (unsigned i = 0; i < G_N_ELEMENTS(dsd2pcm); ++i) + if (dsd2pcm[i] != nullptr) + dsd2pcm_reset(dsd2pcm[i]); +} + +const float * +PcmDsd::ToFloat(unsigned channels, bool lsbfirst, + const uint8_t *src, size_t src_size, + size_t *dest_size_r) +{ + assert(src != nullptr); + assert(src_size > 0); + assert(src_size % channels == 0); + assert(channels <= G_N_ELEMENTS(dsd2pcm)); + + const unsigned num_samples = src_size; + const unsigned num_frames = src_size / channels; + + float *dest; + const size_t dest_size = num_samples * sizeof(*dest); + *dest_size_r = dest_size; + dest = (float *)buffer.Get(dest_size); + + for (unsigned c = 0; c < channels; ++c) { + if (dsd2pcm[c] == nullptr) { + dsd2pcm[c] = dsd2pcm_init(); + if (dsd2pcm[c] == nullptr) + return nullptr; + } + + dsd2pcm_translate(dsd2pcm[c], num_frames, + src + c, channels, + lsbfirst, dest + c, channels); + } + + return dest; +} diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx new file mode 100644 index 000000000..26ee11b13 --- /dev/null +++ b/src/pcm/PcmDsd.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_DSD_HXX +#define MPD_PCM_DSD_HXX + +#include "check.h" +#include "PcmBuffer.hxx" + +#include <stdint.h> + +/** + * Wrapper for the dsd2pcm library. + */ +struct PcmDsd { + PcmBuffer buffer; + + struct dsd2pcm_ctx_s *dsd2pcm[32]; + + PcmDsd(); + ~PcmDsd(); + + void Reset(); + + const float *ToFloat(unsigned channels, bool lsbfirst, + const uint8_t *src, size_t src_size, + size_t *dest_size_r); +}; + +#endif diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx new file mode 100644 index 000000000..2d0f33a15 --- /dev/null +++ b/src/pcm/PcmDsdUsb.cxx @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmDsdUsb.hxx" +#include "PcmBuffer.hxx" +#include "AudioFormat.hxx" + +constexpr +static inline uint32_t +pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b) +{ + return 0xff050000 | (a << 8) | b; +} + +constexpr +static inline uint32_t +pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b) +{ + return 0xfffa0000 | (a << 8) | b; +} + + +const uint32_t * +pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels, + const uint8_t *src, size_t src_size, + size_t *dest_size_r) +{ + assert(audio_valid_channel_count(channels)); + assert(src != NULL); + assert(src_size > 0); + assert(src_size % channels == 0); + + const unsigned num_src_samples = src_size; + const unsigned num_src_frames = num_src_samples / channels; + + /* this rounds down and discards the last odd frame; not + elegant, but good enough for now */ + const unsigned num_frames = num_src_frames / 2; + const unsigned num_samples = num_frames * channels; + + const size_t dest_size = num_samples * 4; + *dest_size_r = dest_size; + uint32_t *const dest0 = (uint32_t *)buffer.Get(dest_size), + *dest = dest0; + + for (unsigned i = num_frames / 2; i > 0; --i) { + for (unsigned c = channels; c > 0; --c) { + /* each 24 bit sample has 16 DSD sample bits + plus the magic 0x05 marker */ + + *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]); + + /* seek the source pointer to the next + channel */ + ++src; + } + + /* skip the second byte of each channel, because we + have already copied it */ + src += channels; + + for (unsigned c = channels; c > 0; --c) { + /* each 24 bit sample has 16 DSD sample bits + plus the magic 0xfa marker */ + + *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]); + + /* seek the source pointer to the next + channel */ + ++src; + } + + /* skip the second byte of each channel, because we + have already copied it */ + src += channels; + } + + return dest0; +} diff --git a/src/pcm/PcmDsdUsb.hxx b/src/pcm/PcmDsdUsb.hxx new file mode 100644 index 000000000..3b7121465 --- /dev/null +++ b/src/pcm/PcmDsdUsb.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_DSD_USB_HXX +#define MPD_PCM_DSD_USB_HXX + +#include "check.h" + +#include <stdint.h> +#include <stddef.h> + +class PcmBuffer; + +/** + * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for + * playback over USB, according to the proposed standard by + * dCS and others: + * http://www.sonore.us/DoP_openStandard_1v1.pdf + */ +const uint32_t * +pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels, + const uint8_t *src, size_t src_size, + size_t *dest_size_r); + +#endif diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx new file mode 100644 index 000000000..762411f59 --- /dev/null +++ b/src/pcm/PcmExport.cxx @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmExport.hxx" +#include "PcmDsdUsb.hxx" + +extern "C" { +#include "pcm_pack.h" +#include "util/byte_reverse.h" +} + +void +PcmExport::Open(SampleFormat sample_format, unsigned _channels, + bool _dsd_usb, bool _shift8, bool _pack, bool _reverse_endian) +{ + assert(audio_valid_sample_format(sample_format)); + assert(!_dsd_usb || audio_valid_channel_count(_channels)); + + channels = _channels; + dsd_usb = _dsd_usb && sample_format == SampleFormat::DSD; + if (dsd_usb) + /* after the conversion to DSD-over-USB, the DSD + samples are stuffed inside fake 24 bit samples */ + sample_format = SampleFormat::S24_P32; + + shift8 = _shift8 && sample_format == SampleFormat::S24_P32; + pack24 = _pack && sample_format == SampleFormat::S24_P32; + + assert(!shift8 || !pack24); + + reverse_endian = 0; + if (_reverse_endian) { + size_t sample_size = pack24 + ? 3 + : sample_format_size(sample_format); + assert(sample_size <= 0xff); + + if (sample_size > 1) + reverse_endian = sample_size; + } +} + +size_t +PcmExport::GetFrameSize(const AudioFormat &audio_format) const +{ + if (pack24) + /* packed 24 bit samples (3 bytes per sample) */ + return audio_format.channels * 3; + + if (dsd_usb) + /* the DSD-over-USB draft says that DSD 1-bit samples + are enclosed within 24 bit samples, and MPD's + representation of 24 bit is padded to 32 bit (4 + bytes per sample) */ + return channels * 4; + + return audio_format.GetFrameSize(); +} + +const void * +PcmExport::Export(const void *data, size_t size, size_t &dest_size_r) +{ + if (dsd_usb) + data = pcm_dsd_to_usb(dsd_buffer, channels, + (const uint8_t *)data, size, &size); + + if (pack24) { + assert(size % 4 == 0); + + const size_t num_samples = size / 4; + const size_t dest_size = num_samples * 3; + + const uint8_t *src8 = (const uint8_t *)data; + const uint8_t *src_end8 = src8 + size; + uint8_t *dest = (uint8_t *)pack_buffer.Get(dest_size); + assert(dest != NULL); + + pcm_pack_24(dest, (const int32_t *)src8, + (const int32_t *)src_end8); + + data = dest; + size = dest_size; + } else if (shift8) { + assert(size % 4 == 0); + + const uint8_t *src8 = (const uint8_t *)data; + const uint8_t *src_end8 = src8 + size; + const uint32_t *src = (const uint32_t *)src8; + const uint32_t *const src_end = (const uint32_t *)src_end8; + + uint32_t *dest = (uint32_t *)pack_buffer.Get(size); + data = dest; + + while (src < src_end) + *dest++ = *src++ << 8; + } + + + if (reverse_endian > 0) { + assert(reverse_endian >= 2); + + uint8_t *dest = (uint8_t *)reverse_buffer.Get(size); + assert(dest != NULL); + + const uint8_t *src = (const uint8_t *)data; + const uint8_t *src_end = src + size; + reverse_bytes(dest, src, src_end, reverse_endian); + + data = dest; + } + + dest_size_r = size; + return data; +} + +size_t +PcmExport::CalcSourceSize(size_t size) const +{ + if (pack24) + /* 32 bit to 24 bit conversion (4 to 3 bytes) */ + size = (size / 3) * 4; + + if (dsd_usb) + /* DSD over USB doubles the transport size */ + size /= 2; + + return size; +} diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx new file mode 100644 index 000000000..bd18c0534 --- /dev/null +++ b/src/pcm/PcmExport.hxx @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PCM_EXPORT_HXX +#define PCM_EXPORT_HXX + +#include "check.h" +#include "PcmBuffer.hxx" +#include "AudioFormat.hxx" + +struct AudioFormat; + +/** + * An object that handles export of PCM samples to some instance + * outside of MPD. It has a few more options to tweak the binary + * representation which are not supported by the pcm_convert library. + */ +struct PcmExport { + /** + * The buffer is used to convert DSD samples to the + * DSD-over-USB format. + * + * @see #dsd_usb + */ + PcmBuffer dsd_buffer; + + /** + * The buffer is used to pack samples, removing padding. + * + * @see #pack24 + */ + PcmBuffer pack_buffer; + + /** + * The buffer is used to reverse the byte order. + * + * @see #reverse_endian + */ + PcmBuffer reverse_buffer; + + /** + * The number of channels. + */ + uint8_t channels; + + /** + * Convert DSD to DSD-over-USB? Input format must be + * SampleFormat::DSD and output format must be + * SampleFormat::S24_P32. + */ + bool dsd_usb; + + /** + * Convert (padded) 24 bit samples to 32 bit by shifting 8 + * bits to the left? + */ + bool shift8; + + /** + * Pack 24 bit samples? + */ + bool pack24; + + /** + * Export the samples in reverse byte order? A non-zero value + * means the option is enabled and represents the size of each + * sample (2 or bigger). + */ + uint8_t reverse_endian; + + /** + * Open the #pcm_export_state object. + * + * There is no "close" method. This function may be called multiple + * times to reuse the object, until pcm_export_deinit() is called. + * + * This function cannot fail. + * + * @param channels the number of channels; ignored unless dsd_usb is set + */ + void Open(SampleFormat sample_format, unsigned channels, + bool dsd_usb, bool shift8, bool pack, bool reverse_endian); + + /** + * Calculate the size of one output frame. + */ + gcc_pure + size_t GetFrameSize(const AudioFormat &audio_format) const; + + /** + * Export a PCM buffer. + * + * @param state an initialized and open pcm_export_state object + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer (may be a pointer to the source buffer) + */ + const void *Export(const void *src, size_t src_size, + size_t &dest_size_r); + + /** + * Converts the number of consumed bytes from the pcm_export() + * destination buffer to the according number of bytes from the + * pcm_export() source buffer. + */ + gcc_pure + size_t CalcSourceSize(size_t dest_size) const; +}; + +#endif diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx new file mode 100644 index 000000000..6425c7cfd --- /dev/null +++ b/src/pcm/PcmFormat.cxx @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmFormat.hxx" +#include "PcmDither.hxx" +#include "PcmBuffer.hxx" +#include "pcm_pack.h" +#include "PcmUtils.hxx" + +#include <type_traits> + +template<typename S> +struct DefaultSampleBits { + typedef decltype(*S()) T; + typedef typename std::remove_reference<T>::type U; + + static constexpr auto value = sizeof(U) * 8; +}; + +static void +pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) +{ + while (in < in_end) { + *out++ = *in++ << 8; + } +} + +static void +pcm_convert_24_to_16(PcmDither &dither, + int16_t *out, const int32_t *in, const int32_t *in_end) +{ + dither.Dither24To16(out, in, in_end); +} + +static void +pcm_convert_32_to_16(PcmDither &dither, + int16_t *out, const int32_t *in, const int32_t *in_end) +{ + dither.Dither32To16(out, in, in_end); +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertFromFloat(S dest, const float *src, const float *end) +{ + typedef decltype(*S()) T; + typedef typename std::remove_reference<T>::type U; + + const float factor = 1 << (bits - 1); + + while (src != end) { + int sample(*src++ * factor); + *dest++ = PcmClamp<U, int, bits>(sample); + } +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertFromFloat(S dest, const float *src, size_t size) +{ + ConvertFromFloat<S, bits>(dest, src, pcm_end_pointer(src, size)); +} + +template<typename S, unsigned bits=sizeof(S)*8> +static S * +AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size, + size_t *dest_size_r) +{ + constexpr size_t src_sample_size = sizeof(*src); + assert(src_size % src_sample_size == 0); + + const size_t num_samples = src_size / src_sample_size; + *dest_size_r = num_samples * sizeof(S); + S *dest = (S *)buffer.Get(*dest_size_r); + ConvertFromFloat<S *, bits>(dest, src, src_size); + return dest; +} + +static int16_t * +pcm_allocate_8_to_16(PcmBuffer &buffer, + const int8_t *src, size_t src_size, size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = (int16_t *)buffer.Get(*dest_size_r); + pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_24p32_to_16(PcmBuffer &buffer, PcmDither &dither, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int16_t *)buffer.Get(*dest_size_r); + pcm_convert_24_to_16(dither, dest, src, + pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_32_to_16(PcmBuffer &buffer, PcmDither &dither, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int16_t *)buffer.Get(*dest_size_r); + pcm_convert_32_to_16(dither, dest, src, + pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_float_to_16(PcmBuffer &buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateFromFloat<int16_t>(buffer, src, src_size, dest_size_r); +} + +const int16_t * +pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % sample_format_size(src_format) == 0); + + switch (src_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + break; + + case SampleFormat::S8: + return pcm_allocate_8_to_16(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SampleFormat::S16: + *dest_size_r = src_size; + return (const int16_t *)src; + + case SampleFormat::S24_P32: + return pcm_allocate_24p32_to_16(buffer, dither, + (const int32_t *)src, src_size, + dest_size_r); + + case SampleFormat::S32: + return pcm_allocate_32_to_16(buffer, dither, + (const int32_t *)src, src_size, + dest_size_r); + + case SampleFormat::FLOAT: + return pcm_allocate_float_to_16(buffer, + (const float *)src, src_size, + dest_size_r); + } + + return NULL; +} + +static void +pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 16; +} + +static void +pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 8; +} + +static void +pcm_convert_32_to_24(int32_t *restrict out, + const int32_t *restrict in, + const int32_t *restrict in_end) +{ + while (in < in_end) + *out++ = *in++ >> 8; +} + +static int32_t * +pcm_allocate_8_to_24(PcmBuffer &buffer, + const int8_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = (int32_t *)buffer.Get(*dest_size_r); + pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_16_to_24(PcmBuffer &buffer, + const int16_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int32_t *)buffer.Get(*dest_size_r); + pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_32_to_24(PcmBuffer &buffer, + const int32_t *src, size_t src_size, size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = (int32_t *)buffer.Get(*dest_size_r); + pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_float_to_24(PcmBuffer &buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateFromFloat<int32_t, 24>(buffer, src, src_size, + dest_size_r); +} + +const int32_t * +pcm_convert_to_24(PcmBuffer &buffer, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % sample_format_size(src_format) == 0); + + switch (src_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + break; + + case SampleFormat::S8: + return pcm_allocate_8_to_24(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SampleFormat::S16: + return pcm_allocate_16_to_24(buffer, + (const int16_t *)src, src_size, + dest_size_r); + + case SampleFormat::S24_P32: + *dest_size_r = src_size; + return (const int32_t *)src; + + case SampleFormat::S32: + return pcm_allocate_32_to_24(buffer, + (const int32_t *)src, src_size, + dest_size_r); + + case SampleFormat::FLOAT: + return pcm_allocate_float_to_24(buffer, + (const float *)src, src_size, + dest_size_r); + } + + return NULL; +} + +static void +pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 24; +} + +static void +pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) +{ + while (in < in_end) + *out++ = *in++ << 16; +} + +static void +pcm_convert_24_to_32(int32_t *restrict out, + const int32_t *restrict in, + const int32_t *restrict in_end) +{ + while (in < in_end) + *out++ = *in++ << 8; +} + +static int32_t * +pcm_allocate_8_to_32(PcmBuffer &buffer, + const int8_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = (int32_t *)buffer.Get(*dest_size_r); + pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_16_to_32(PcmBuffer &buffer, + const int16_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = (int32_t *)buffer.Get(*dest_size_r); + pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_24p32_to_32(PcmBuffer &buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = (int32_t *)buffer.Get(*dest_size_r); + pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_float_to_32(PcmBuffer &buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + /* convert to S24_P32 first */ + int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, + dest_size_r); + + /* convert to 32 bit in-place */ + pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); + return dest; +} + +const int32_t * +pcm_convert_to_32(PcmBuffer &buffer, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + assert(src_size % sample_format_size(src_format) == 0); + + switch (src_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + break; + + case SampleFormat::S8: + return pcm_allocate_8_to_32(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SampleFormat::S16: + return pcm_allocate_16_to_32(buffer, + (const int16_t *)src, src_size, + dest_size_r); + + case SampleFormat::S24_P32: + return pcm_allocate_24p32_to_32(buffer, + (const int32_t *)src, src_size, + dest_size_r); + + case SampleFormat::S32: + *dest_size_r = src_size; + return (const int32_t *)src; + + case SampleFormat::FLOAT: + return pcm_allocate_float_to_32(buffer, + (const float *)src, src_size, + dest_size_r); + } + + return NULL; +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertToFloat(float *dest, S src, S end) +{ + constexpr float factor = 0.5 / (1 << (bits - 2)); + while (src != end) + *dest++ = float(*src++) * factor; + +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static void +ConvertToFloat(float *dest, S src, size_t size) +{ + ConvertToFloat<S, bits>(dest, src, pcm_end_pointer(src, size)); +} + +template<typename S, unsigned bits=DefaultSampleBits<S>::value> +static float * +AllocateToFloat(PcmBuffer &buffer, S src, size_t src_size, + size_t *dest_size_r) +{ + constexpr size_t src_sample_size = sizeof(*S()); + assert(src_size % src_sample_size == 0); + + const size_t num_samples = src_size / src_sample_size; + *dest_size_r = num_samples * sizeof(float); + float *dest = (float *)buffer.Get(*dest_size_r); + ConvertToFloat<S, bits>(dest, src, src_size); + return dest; +} + +static float * +pcm_allocate_8_to_float(PcmBuffer &buffer, + const int8_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat(buffer, src, src_size, dest_size_r); +} + +static float * +pcm_allocate_16_to_float(PcmBuffer &buffer, + const int16_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat(buffer, src, src_size, dest_size_r); +} + +static float * +pcm_allocate_24p32_to_float(PcmBuffer &buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat<decltype(src), 24> + (buffer, src, src_size, dest_size_r); +} + +static float * +pcm_allocate_32_to_float(PcmBuffer &buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + return AllocateToFloat(buffer, src, src_size, dest_size_r); +} + +const float * +pcm_convert_to_float(PcmBuffer &buffer, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + switch (src_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + break; + + case SampleFormat::S8: + return pcm_allocate_8_to_float(buffer, + (const int8_t *)src, src_size, + dest_size_r); + + case SampleFormat::S16: + return pcm_allocate_16_to_float(buffer, + (const int16_t *)src, src_size, + dest_size_r); + + case SampleFormat::S24_P32: + return pcm_allocate_24p32_to_float(buffer, + (const int32_t *)src, src_size, + dest_size_r); + + case SampleFormat::S32: + return pcm_allocate_32_to_float(buffer, + (const int32_t *)src, src_size, + dest_size_r); + + case SampleFormat::FLOAT: + *dest_size_r = src_size; + return (const float *)src; + } + + return NULL; +} diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx new file mode 100644 index 000000000..cc44d6dd5 --- /dev/null +++ b/src/pcm/PcmFormat.hxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FORMAT_HXX +#define MPD_PCM_FORMAT_HXX + +#include "AudioFormat.hxx" + +#include <stdint.h> +#include <stddef.h> + +class PcmBuffer; +class PcmDither; + +/** + * Converts PCM samples to 16 bit. If the source format is 24 bit, + * then dithering is applied. + * + * @param buffer a PcmBuffer object + * @param dither a pcm_dither object for 24-to-16 conversion + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int16_t * +pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +/** + * Converts PCM samples to 24 bit (32 bit alignment). + * + * @param buffer a PcmBuffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_to_24(PcmBuffer &buffer, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +/** + * Converts PCM samples to 32 bit. + * + * @param buffer a PcmBuffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const int32_t * +pcm_convert_to_32(PcmBuffer &buffer, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +/** + * Converts PCM samples to 32 bit floating point. + * + * @param buffer a PcmBuffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_to_float(PcmBuffer &buffer, + SampleFormat src_format, const void *src, + size_t src_size, size_t *dest_size_r); + +#endif diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx new file mode 100644 index 000000000..f4a02fc47 --- /dev/null +++ b/src/pcm/PcmMix.cxx @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmMix.hxx" +#include "PcmVolume.hxx" +#include "PcmUtils.hxx" +#include "AudioFormat.hxx" + +#include <math.h> + +template<typename T, typename U, unsigned bits> +static T +PcmAddVolume(T _a, T _b, int volume1, int volume2) +{ + U a(_a), b(_b); + + U c = ((a * volume1 + b * volume2) + + pcm_volume_dither() + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + return PcmClamp<T, U, bits>(c); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2) +{ + for (size_t i = 0; i != n; ++i) + a[i] = PcmAddVolume<T, U, bits>(a[i], b[i], volume1, volume2); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2) +{ + constexpr size_t sample_size = sizeof(T); + assert(size % sample_size == 0); + + PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size, + volume1, volume2); +} + +static void +pcm_add_vol_float(float *buffer1, const float *buffer2, + unsigned num_samples, float volume1, float volume2) +{ + while (num_samples > 0) { + float sample1 = *buffer1; + float sample2 = *buffer2++; + + sample1 = (sample1 * volume1 + sample2 * volume2); + *buffer1++ = sample1; + --num_samples; + } +} + +static bool +pcm_add_vol(void *buffer1, const void *buffer2, size_t size, + int vol1, int vol2, + SampleFormat format) +{ + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + /* not implemented */ + return false; + + case SampleFormat::S8: + PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SampleFormat::S16: + PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SampleFormat::S24_P32: + PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SampleFormat::S32: + PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size, + vol1, vol2); + return true; + + case SampleFormat::FLOAT: + pcm_add_vol_float((float *)buffer1, (const float *)buffer2, + size / 4, + pcm_volume_to_float(vol1), + pcm_volume_to_float(vol2)); + return true; + } + + assert(false); + gcc_unreachable(); +} + +template<typename T, typename U, unsigned bits> +static T +PcmAdd(T _a, T _b) +{ + U a(_a), b(_b); + return PcmClamp<T, U, bits>(a + b); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAdd(T *a, const T *b, unsigned n) +{ + for (size_t i = 0; i != n; ++i) + a[i] = PcmAdd<T, U, bits>(a[i], b[i]); +} + +template<typename T, typename U, unsigned bits> +static void +PcmAddVoid(void *a, const void *b, size_t size) +{ + constexpr size_t sample_size = sizeof(T); + assert(size % sample_size == 0); + + PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size); +} + +static void +pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + float sample1 = *buffer1; + float sample2 = *buffer2++; + *buffer1++ = sample1 + sample2; + --num_samples; + } +} + +static bool +pcm_add(void *buffer1, const void *buffer2, size_t size, + SampleFormat format) +{ + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + /* not implemented */ + return false; + + case SampleFormat::S8: + PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size); + return true; + + case SampleFormat::S16: + PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size); + return true; + + case SampleFormat::S24_P32: + PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size); + return true; + + case SampleFormat::S32: + PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size); + return true; + + case SampleFormat::FLOAT: + pcm_add_float((float *)buffer1, (const float *)buffer2, + size / 4); + return true; + } + + assert(false); + gcc_unreachable(); +} + +bool +pcm_mix(void *buffer1, const void *buffer2, size_t size, + SampleFormat format, float portion1) +{ + int vol1; + float s; + + /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN + * to signal mixing rather than fading */ + if (isnan(portion1)) + return pcm_add(buffer1, buffer2, size, format); + + 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); + + return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); +} diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx new file mode 100644 index 000000000..b50a163fd --- /dev/null +++ b/src/pcm/PcmMix.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MIX_HXX +#define MPD_PCM_MIX_HXX + +#include "AudioFormat.hxx" +#include "gcc.h" + +#include <stddef.h> + +/* + * Linearly mixes two PCM buffers. Both must have the same length and + * the same audio format. The formula is: + * + * s1 := s1 * portion1 + s2 * (1 - portion1) + * + * @param buffer1 the first PCM buffer, and the destination buffer + * @param buffer2 the second PCM buffer + * @param size the size of both buffers in bytes + * @param format the sample format of both buffers + * @param portion1 a number between 0.0 and 1.0 specifying the portion + * of the first buffer in the mix; portion2 = (1.0 - portion1). The value + * NaN is used by the MixRamp code to specify that simple addition is required. + * + * @return true on success, false if the format is not supported + */ +gcc_warn_unused_result +bool +pcm_mix(void *buffer1, const void *buffer2, size_t size, + SampleFormat format, float portion1); + +#endif diff --git a/src/pcm/PcmPrng.hxx b/src/pcm/PcmPrng.hxx new file mode 100644 index 000000000..0c823250d --- /dev/null +++ b/src/pcm/PcmPrng.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PRNG_HXX +#define MPD_PCM_PRNG_HXX + +/** + * A very simple linear congruential PRNG. It's good enough for PCM + * dithering. + */ +static unsigned long +pcm_prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +#endif diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx new file mode 100644 index 000000000..e2ce095d1 --- /dev/null +++ b/src/pcm/PcmResample.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmResampleInternal.hxx" + +#ifdef HAVE_LIBSAMPLERATE +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#endif + +#include <string.h> + +#ifdef HAVE_LIBSAMPLERATE +static bool lsr_enabled; +#endif + +#ifdef HAVE_LIBSAMPLERATE +static bool +pcm_resample_lsr_enabled(void) +{ + return lsr_enabled; +} +#endif + +bool +pcm_resample_global_init(Error &error) +{ +#ifdef HAVE_LIBSAMPLERATE + const char *converter = + config_get_string(CONF_SAMPLERATE_CONVERTER, ""); + + lsr_enabled = strcmp(converter, "internal") != 0; + if (lsr_enabled) + return pcm_resample_lsr_global_init(converter, error); + else + return true; +#else + (void)error; + return true; +#endif +} + +PcmResampler::PcmResampler() +{ +#ifdef HAVE_LIBSAMPLERATE + if (pcm_resample_lsr_enabled()) + pcm_resample_lsr_init(this); +#endif +} + +PcmResampler::~PcmResampler() +{ +#ifdef HAVE_LIBSAMPLERATE + if (pcm_resample_lsr_enabled()) + pcm_resample_lsr_deinit(this); +#endif +} + +void +PcmResampler::Reset() +{ +#ifdef HAVE_LIBSAMPLERATE + pcm_resample_lsr_reset(this); +#endif +} + +const float * +PcmResampler::ResampleFloat(unsigned channels, unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r) +{ +#ifdef HAVE_LIBSAMPLERATE + if (pcm_resample_lsr_enabled()) + return pcm_resample_lsr_float(this, channels, + src_rate, src_buffer, src_size, + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; +#endif + + /* sizeof(float)==sizeof(int32_t); the fallback resampler does + not do any math on the sample values, so this hack is + possible: */ + return (const float *) + pcm_resample_fallback_32(this, channels, + src_rate, (const int32_t *)src_buffer, + src_size, + dest_rate, dest_size_r); +} + +const int16_t * +PcmResampler::Resample16(unsigned channels, + unsigned src_rate, const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r) +{ +#ifdef HAVE_LIBSAMPLERATE + if (pcm_resample_lsr_enabled()) + return pcm_resample_lsr_16(this, channels, + src_rate, src_buffer, src_size, + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; +#endif + + return pcm_resample_fallback_16(this, channels, + src_rate, src_buffer, src_size, + dest_rate, dest_size_r); +} + +const int32_t * +PcmResampler::Resample32(unsigned channels, unsigned src_rate, + const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r) +{ +#ifdef HAVE_LIBSAMPLERATE + if (pcm_resample_lsr_enabled()) + return pcm_resample_lsr_32(this, channels, + src_rate, src_buffer, src_size, + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; +#endif + + return pcm_resample_fallback_32(this, channels, + src_rate, src_buffer, src_size, + dest_rate, dest_size_r); +} diff --git a/src/pcm/PcmResample.hxx b/src/pcm/PcmResample.hxx new file mode 100644 index 000000000..8a740744a --- /dev/null +++ b/src/pcm/PcmResample.hxx @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_RESAMPLE_HXX +#define MPD_PCM_RESAMPLE_HXX + +#include "check.h" +#include "PcmBuffer.hxx" + +#include <stdint.h> +#include <stddef.h> + +#ifdef HAVE_LIBSAMPLERATE +#include <samplerate.h> +#endif + +class Error; + +/** + * This object is statically allocated (within another struct), and + * holds buffer allocations and the state for the resampler. + */ +struct PcmResampler { +#ifdef HAVE_LIBSAMPLERATE + SRC_STATE *state; + SRC_DATA data; + + PcmBuffer in, out; + + struct { + unsigned src_rate; + unsigned dest_rate; + unsigned channels; + } prev; + + int error; +#endif + + PcmBuffer buffer; + + PcmResampler(); + ~PcmResampler(); + + /** + * @see pcm_convert_reset() + */ + void Reset(); + + /** + * Resamples 32 bit float data. + * + * @param channels the number of channels + * @param src_rate the source sample rate + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_rate the requested destination sample rate + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ + const float *ResampleFloat(unsigned channels, unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r); + + /** + * Resamples 16 bit PCM data. + * + * @param channels the number of channels + * @param src_rate the source sample rate + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_rate the requested destination sample rate + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ + const int16_t *Resample16(unsigned channels, unsigned src_rate, + const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r); + + /** + * Resamples 32 bit PCM data. + * + * @param channels the number of channels + * @param src_rate the source sample rate + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_rate the requested destination sample rate + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ + const int32_t *Resample32(unsigned channels, unsigned src_rate, + const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r); + + /** + * Resamples 24 bit PCM data. + * + * @param channels the number of channels + * @param src_rate the source sample rate + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_rate the requested destination sample rate + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ + const int32_t *Resample24(unsigned channels, unsigned src_rate, + const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error_r) + { + /* reuse the 32 bit code - the resampler code doesn't care if + the upper 8 bits are actually used */ + return Resample32(channels, src_rate, src_buffer, src_size, + dest_rate, dest_size_r, error_r); + } +}; + +bool +pcm_resample_global_init(Error &error); + +#endif diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx new file mode 100644 index 000000000..a62cd64f7 --- /dev/null +++ b/src/pcm/PcmResampleFallback.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmResampleInternal.hxx" + +#include <assert.h> + +/* resampling code blatantly ripped from ESD */ +const int16_t * +pcm_resample_fallback_16(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, + size_t *dest_size_r) +{ + unsigned dest_pos = 0; + unsigned src_frames = src_size / channels / sizeof(*src_buffer); + unsigned dest_frames = + (src_frames * dest_rate + src_rate - 1) / src_rate; + unsigned dest_samples = dest_frames * channels; + size_t dest_size = dest_samples * sizeof(*src_buffer); + int16_t *dest_buffer = (int16_t *)state->buffer.Get(dest_size); + + assert((src_size % (sizeof(*src_buffer) * channels)) == 0); + + switch (channels) { + case 1: + while (dest_pos < dest_samples) { + unsigned src_pos = dest_pos * src_rate / dest_rate; + + dest_buffer[dest_pos++] = src_buffer[src_pos]; + } + break; + case 2: + while (dest_pos < dest_samples) { + unsigned src_pos = dest_pos * src_rate / dest_rate; + src_pos &= ~1; + + dest_buffer[dest_pos++] = src_buffer[src_pos]; + dest_buffer[dest_pos++] = src_buffer[src_pos + 1]; + } + break; + } + + *dest_size_r = dest_size; + return dest_buffer; +} + +const int32_t * +pcm_resample_fallback_32(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, + size_t *dest_size_r) +{ + unsigned dest_pos = 0; + unsigned src_frames = src_size / channels / sizeof(*src_buffer); + unsigned dest_frames = + (src_frames * dest_rate + src_rate - 1) / src_rate; + unsigned dest_samples = dest_frames * channels; + size_t dest_size = dest_samples * sizeof(*src_buffer); + int32_t *dest_buffer = (int32_t *)state->buffer.Get(dest_size); + + assert((src_size % (sizeof(*src_buffer) * channels)) == 0); + + switch (channels) { + case 1: + while (dest_pos < dest_samples) { + unsigned src_pos = dest_pos * src_rate / dest_rate; + + dest_buffer[dest_pos++] = src_buffer[src_pos]; + } + break; + case 2: + while (dest_pos < dest_samples) { + unsigned src_pos = dest_pos * src_rate / dest_rate; + src_pos &= ~1; + + dest_buffer[dest_pos++] = src_buffer[src_pos]; + dest_buffer[dest_pos++] = src_buffer[src_pos + 1]; + } + break; + } + + *dest_size_r = dest_size; + return dest_buffer; +} diff --git a/src/pcm/PcmResampleInternal.hxx b/src/pcm/PcmResampleInternal.hxx new file mode 100644 index 000000000..59bb2f5df --- /dev/null +++ b/src/pcm/PcmResampleInternal.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 declarations for the pcm_resample library. The "internal" + * resampler is called "fallback" in the MPD source, so the file name + * of this header is somewhat unrelated to it. + */ + +#ifndef MPD_PCM_RESAMPLE_INTERNAL_HXX +#define MPD_PCM_RESAMPLE_INTERNAL_HXX + +#include "check.h" +#include "PcmResample.hxx" + +#ifdef HAVE_LIBSAMPLERATE + +bool +pcm_resample_lsr_global_init(const char *converter, Error &error); + +void +pcm_resample_lsr_init(PcmResampler *state); + +void +pcm_resample_lsr_deinit(PcmResampler *state); + +void +pcm_resample_lsr_reset(PcmResampler *state); + +const float * +pcm_resample_lsr_float(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error); + +const int16_t * +pcm_resample_lsr_16(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error); + +const int32_t * +pcm_resample_lsr_32(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int32_t *src_buffer, + size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error); + +#endif + +const int16_t * +pcm_resample_fallback_16(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, + size_t *dest_size_r); + +const int32_t * +pcm_resample_fallback_32(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int32_t *src_buffer, + size_t src_size, + unsigned dest_rate, + size_t *dest_size_r); + +#endif diff --git a/src/pcm/PcmResampleLibsamplerate.cxx b/src/pcm/PcmResampleLibsamplerate.cxx new file mode 100644 index 000000000..2ffe4b8c4 --- /dev/null +++ b/src/pcm/PcmResampleLibsamplerate.cxx @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PcmResampleInternal.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +static int lsr_converter = SRC_SINC_FASTEST; + +static constexpr Domain libsamplerate_domain("libsamplerate"); + +static bool +lsr_parse_converter(const char *s) +{ + assert(s != nullptr); + + if (*s == 0) + return true; + + char *endptr; + long l = strtol(s, &endptr, 10); + if (*endptr == 0 && src_get_name(l) != nullptr) { + lsr_converter = l; + return true; + } + + size_t length = strlen(s); + for (int i = 0;; ++i) { + const char *name = src_get_name(i); + if (name == nullptr) + break; + + if (g_ascii_strncasecmp(s, name, length) == 0) { + lsr_converter = i; + return true; + } + } + + return false; +} + +bool +pcm_resample_lsr_global_init(const char *converter, Error &error) +{ + if (!lsr_parse_converter(converter)) { + error.Format(libsamplerate_domain, + "unknown samplerate converter '%s'", converter); + return false; + } + + FormatDebug(libsamplerate_domain, + "libsamplerate converter '%s'", + src_get_name(lsr_converter)); + + return true; +} + +void +pcm_resample_lsr_init(PcmResampler *state) +{ + state->state = nullptr; + memset(&state->data, 0, sizeof(state->data)); + memset(&state->prev, 0, sizeof(state->prev)); + state->error = 0; +} + +void +pcm_resample_lsr_deinit(PcmResampler *state) +{ + if (state->state != nullptr) + state->state = src_delete(state->state); +} + +void +pcm_resample_lsr_reset(PcmResampler *state) +{ + if (state->state != nullptr) + src_reset(state->state); +} + +static bool +pcm_resample_set(PcmResampler *state, + unsigned channels, unsigned src_rate, unsigned dest_rate, + Error &error_r) +{ + /* (re)set the state/ratio if the in or out format changed */ + if (channels == state->prev.channels && + src_rate == state->prev.src_rate && + dest_rate == state->prev.dest_rate) + return true; + + state->error = 0; + state->prev.channels = channels; + state->prev.src_rate = src_rate; + state->prev.dest_rate = dest_rate; + + if (state->state) + state->state = src_delete(state->state); + + int error; + state->state = src_new(lsr_converter, channels, &error); + if (!state->state) { + error_r.Format(libsamplerate_domain, error, + "libsamplerate initialization has failed: %s", + src_strerror(error)); + return false; + } + + SRC_DATA *data = &state->data; + data->src_ratio = (double)dest_rate / (double)src_rate; + FormatDebug(libsamplerate_domain, + "setting samplerate conversion ratio to %.2lf", + data->src_ratio); + src_set_ratio(state->state, data->src_ratio); + + return true; +} + +static bool +lsr_process(PcmResampler *state, Error &error) +{ + if (state->error == 0) + state->error = src_process(state->state, &state->data); + if (state->error) { + error.Format(libsamplerate_domain, state->error, + "libsamplerate has failed: %s", + src_strerror(state->error)); + return false; + } + + return true; +} + +const float * +pcm_resample_lsr_float(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error) +{ + SRC_DATA *data = &state->data; + + assert((src_size % (sizeof(*src_buffer) * channels)) == 0); + + if (!pcm_resample_set(state, channels, src_rate, dest_rate, error)) + return nullptr; + + data->input_frames = src_size / sizeof(*src_buffer) / channels; + data->data_in = const_cast<float *>(src_buffer); + + data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; + size_t data_out_size = data->output_frames * sizeof(float) * channels; + data->data_out = (float *)state->out.Get(data_out_size); + + if (!lsr_process(state, error)) + return nullptr; + + *dest_size_r = data->output_frames_gen * + sizeof(*data->data_out) * channels; + return data->data_out; +} + +const int16_t * +pcm_resample_lsr_16(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error) +{ + SRC_DATA *data = &state->data; + + assert((src_size % (sizeof(*src_buffer) * channels)) == 0); + + if (!pcm_resample_set(state, channels, src_rate, dest_rate, + error)) + return nullptr; + + data->input_frames = src_size / sizeof(*src_buffer) / channels; + size_t data_in_size = data->input_frames * sizeof(float) * channels; + data->data_in = (float *)state->in.Get(data_in_size); + + data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; + size_t data_out_size = data->output_frames * sizeof(float) * channels; + data->data_out = (float *)state->out.Get(data_out_size); + + src_short_to_float_array(src_buffer, data->data_in, + data->input_frames * channels); + + if (!lsr_process(state, error)) + return nullptr; + + int16_t *dest_buffer; + *dest_size_r = data->output_frames_gen * + sizeof(*dest_buffer) * channels; + dest_buffer = (int16_t *)state->buffer.Get(*dest_size_r); + src_float_to_short_array(data->data_out, dest_buffer, + data->output_frames_gen * channels); + + return dest_buffer; +} + +#ifdef HAVE_LIBSAMPLERATE_NOINT + +/* libsamplerate introduced these functions in v0.1.3 */ + +static void +src_int_to_float_array(const int *in, float *out, int len) +{ + while (len-- > 0) + *out++ = *in++ / (float)(1 << (24 - 1)); +} + +static void +src_float_to_int_array (const float *in, int *out, int len) +{ + while (len-- > 0) + *out++ = *in++ * (float)(1 << (24 - 1)); +} + +#endif + +const int32_t * +pcm_resample_lsr_32(PcmResampler *state, + unsigned channels, + unsigned src_rate, + const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + Error &error) +{ + SRC_DATA *data = &state->data; + + assert((src_size % (sizeof(*src_buffer) * channels)) == 0); + + if (!pcm_resample_set(state, channels, src_rate, dest_rate, + error)) + return nullptr; + + data->input_frames = src_size / sizeof(*src_buffer) / channels; + size_t data_in_size = data->input_frames * sizeof(float) * channels; + data->data_in = (float *)state->in.Get(data_in_size); + + data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; + size_t data_out_size = data->output_frames * sizeof(float) * channels; + data->data_out = (float *)state->out.Get(data_out_size); + + src_int_to_float_array(src_buffer, data->data_in, + data->input_frames * channels); + + if (!lsr_process(state, error)) + return nullptr; + + int32_t *dest_buffer; + *dest_size_r = data->output_frames_gen * + sizeof(*dest_buffer) * channels; + dest_buffer = (int32_t *)state->buffer.Get(*dest_size_r); + src_float_to_int_array(data->data_out, dest_buffer, + data->output_frames_gen * channels); + + return dest_buffer; +} diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx new file mode 100644 index 000000000..d77c4194a --- /dev/null +++ b/src/pcm/PcmUtils.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_UTILS_H +#define MPD_PCM_UTILS_H + +#include "gcc.h" + +#include <limits> + +#include <stdint.h> + +/** + * Add a byte count to the specified pointer. This is a utility + * function to convert a source pointer and a byte count to an "end" + * pointer for use in loops. + */ +template<typename T> +static inline const T * +pcm_end_pointer(const T *p, size_t size) +{ + return (const T *)((const uint8_t *)p + size); +} + +/** + * Check if the value is within the range of the provided bit size, + * and caps it if necessary. + */ +template<typename T, typename U, unsigned bits> +gcc_const +static inline T +PcmClamp(U x) +{ + constexpr U MIN_VALUE = -(U(1) << (bits - 1)); + constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1; + + typedef std::numeric_limits<T> limits; + static_assert(MIN_VALUE >= limits::min(), "out of range"); + static_assert(MAX_VALUE <= limits::max(), "out of range"); + + if (gcc_unlikely(x < MIN_VALUE)) + return T(MIN_VALUE); + + if (gcc_unlikely(x > MAX_VALUE)) + return T(MAX_VALUE); + + return T(x); +} + +#endif diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx new file mode 100644 index 000000000..05ab73c68 --- /dev/null +++ b/src/pcm/PcmVolume.cxx @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmVolume.hxx" +#include "PcmUtils.hxx" +#include "AudioFormat.hxx" + +#include <glib.h> + +#include <stdint.h> +#include <string.h> + +static void +pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) +{ + while (buffer < end) { + int32_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample); + } +} + +static void +pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) +{ + while (buffer < end) { + int32_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample); + } +} + +#ifdef __i386__ +/** + * Optimized volume function for i386. Use the EDX:EAX 2*32 bit + * multiplication result instead of emulating 64 bit multiplication. + */ +static inline int32_t +pcm_volume_sample_24(int32_t sample, int32_t volume, gcc_unused int32_t dither) +{ + int32_t result; + + asm(/* edx:eax = sample * volume */ + "imul %2\n" + + /* "add %3, %1\n" dithering disabled for now, because we + have no overflow check - is dithering really important + here? */ + + /* eax = edx:eax / PCM_VOLUME_1 */ + "sal $22, %%edx\n" + "shr $10, %1\n" + "or %%edx, %1\n" + + : "=a"(result) + : "0"(sample), "r"(volume) /* , "r"(dither) */ + : "edx" + ); + + return result; +} +#endif + +static void +pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) +{ + while (buffer < end) { +#ifdef __i386__ + /* assembly version for i386 */ + int32_t sample = *buffer; + + sample = pcm_volume_sample_24(sample, volume, + pcm_volume_dither()); +#else + /* portable version */ + int64_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; +#endif + *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample); + } +} + +static void +pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) +{ + while (buffer < end) { +#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++ = PcmClamp<int32_t, int64_t, 32>(sample); +#endif + } +} + +static void +pcm_volume_change_float(float *buffer, const float *end, float volume) +{ + while (buffer < end) { + float sample = *buffer; + sample *= volume; + *buffer++ = sample; + } +} + +bool +pcm_volume(void *buffer, size_t length, + SampleFormat format, + int volume) +{ + if (volume == PCM_VOLUME_1) + return true; + + if (volume <= 0) { + memset(buffer, 0, length); + return true; + } + + const void *end = pcm_end_pointer(buffer, length); + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + /* not implemented */ + return false; + + case SampleFormat::S8: + pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end, + volume); + return true; + + case SampleFormat::S16: + pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end, + volume); + return true; + + case SampleFormat::S24_P32: + pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end, + volume); + return true; + + case SampleFormat::S32: + pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end, + volume); + return true; + + case SampleFormat::FLOAT: + pcm_volume_change_float((float *)buffer, (const float *)end, + pcm_volume_to_float(volume)); + return true; + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/PcmVolume.hxx b/src/pcm/PcmVolume.hxx new file mode 100644 index 000000000..8cd82acf7 --- /dev/null +++ b/src/pcm/PcmVolume.hxx @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_VOLUME_HXX +#define MPD_PCM_VOLUME_HXX + +#include "PcmPrng.hxx" +#include "AudioFormat.hxx" + +#include <stdint.h> +#include <stddef.h> + +enum { + /** this value means "100% volume" */ + PCM_VOLUME_1 = 1024, +}; + +struct AudioFormat; + +/** + * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an + * integer volume value (1000 = 100%). + */ +static inline int +pcm_float_to_volume(float volume) +{ + return volume * PCM_VOLUME_1 + 0.5; +} + +static inline float +pcm_volume_to_float(int volume) +{ + return (float)volume / (float)PCM_VOLUME_1; +} + +/** + * Returns the next volume dithering number, between -511 and +511. + * This number is taken from a global PRNG, see pcm_prng(). + */ +static inline int +pcm_volume_dither(void) +{ + static unsigned long state; + uint32_t r; + + r = state = pcm_prng(state); + + return (r & 511) - ((r >> 9) & 511); +} + +/** + * Adjust the volume of the specified PCM buffer. + * + * @param buffer the PCM buffer + * @param length the length of the PCM buffer + * @param format the sample format of the PCM buffer + * @param volume the volume between 0 and #PCM_VOLUME_1 + * @return true on success, false if the audio format is not supported + */ +bool +pcm_volume(void *buffer, size_t length, + SampleFormat format, + int volume); + +#endif diff --git a/src/dsd2pcm/dsd2pcm.c b/src/pcm/dsd2pcm/dsd2pcm.c index 4c7640853..4c7640853 100644 --- a/src/dsd2pcm/dsd2pcm.c +++ b/src/pcm/dsd2pcm/dsd2pcm.c diff --git a/src/dsd2pcm/dsd2pcm.h b/src/pcm/dsd2pcm/dsd2pcm.h index 80e8ce0cc..80e8ce0cc 100644 --- a/src/dsd2pcm/dsd2pcm.h +++ b/src/pcm/dsd2pcm/dsd2pcm.h diff --git a/src/pcm/dsd2pcm/dsd2pcm.hpp b/src/pcm/dsd2pcm/dsd2pcm.hpp new file mode 100644 index 000000000..8f3f55197 --- /dev/null +++ b/src/pcm/dsd2pcm/dsd2pcm.hpp @@ -0,0 +1,39 @@ +#ifndef DSD2PCM_HXX_INCLUDED +#define DSD2PCM_HXX_INCLUDED + +#include <algorithm> +#include <stdexcept> +#include "dsd2pcm.h" + +/** + * C++ PImpl Wrapper for the dsd2pcm C library + */ + +class dxd +{ + dsd2pcm_ctx *handle; +public: + dxd() : handle(dsd2pcm_init()) {} + + dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) {} + + ~dxd() { dsd2pcm_destroy(handle); } + + friend void swap(dxd & a, dxd & b) + { std::swap(a.handle,b.handle); } + + dxd& operator=(dxd x) + { swap(*this,x); return *this; } + + void translate(size_t samples, + const unsigned char *src, ptrdiff_t src_stride, + bool lsbitfirst, + float *dst, ptrdiff_t dst_stride) + { + dsd2pcm_translate(handle,samples,src,src_stride, + lsbitfirst,dst,dst_stride); + } +}; + +#endif // DSD2PCM_HXX_INCLUDED + diff --git a/src/dsd2pcm/info.txt b/src/pcm/dsd2pcm/info.txt index 15ff29245..15ff29245 100644 --- a/src/dsd2pcm/info.txt +++ b/src/pcm/dsd2pcm/info.txt diff --git a/src/dsd2pcm/main.cpp b/src/pcm/dsd2pcm/main.cpp index 0b58888a8..0b58888a8 100644 --- a/src/dsd2pcm/main.cpp +++ b/src/pcm/dsd2pcm/main.cpp diff --git a/src/dsd2pcm/noiseshape.c b/src/pcm/dsd2pcm/noiseshape.c index ecd2f251d..ecd2f251d 100644 --- a/src/dsd2pcm/noiseshape.c +++ b/src/pcm/dsd2pcm/noiseshape.c diff --git a/src/dsd2pcm/noiseshape.h b/src/pcm/dsd2pcm/noiseshape.h index 6075f0d88..6075f0d88 100644 --- a/src/dsd2pcm/noiseshape.h +++ b/src/pcm/dsd2pcm/noiseshape.h diff --git a/src/pcm/dsd2pcm/noiseshape.hpp b/src/pcm/dsd2pcm/noiseshape.hpp new file mode 100644 index 000000000..1fc698b36 --- /dev/null +++ b/src/pcm/dsd2pcm/noiseshape.hpp @@ -0,0 +1,43 @@ +#ifndef NOISE_SHAPE_HXX_INCLUDED +#define NOISE_SHAPE_HXX_INCLUDED + +#include <stdexcept> +#include "noiseshape.h" + +/** + * C++ wrapper for the noiseshape C library + */ + +class noise_shaper +{ + noise_shape_ctx ctx; +public: + noise_shaper(int sos_count, const float *bbaa) + { + noise_shape_init(&ctx, sos_count, bbaa); + } + + noise_shaper(noise_shaper const& x) + { + noise_shape_clone(&x.ctx,&ctx); + } + + ~noise_shaper() + { noise_shape_destroy(&ctx); } + + noise_shaper& operator=(noise_shaper const& x) + { + if (this != &x) { + noise_shape_destroy(&ctx); + noise_shape_clone(&x.ctx,&ctx); + } + return *this; + } + + float get() { return noise_shape_get(&ctx); } + + void update(float error) { noise_shape_update(&ctx,error); } +}; + +#endif /* NOISE_SHAPE_HXX_INCLUDED */ + diff --git a/src/pcm_pack.c b/src/pcm/pcm_pack.c index 921d880c0..921d880c0 100644 --- a/src/pcm_pack.c +++ b/src/pcm/pcm_pack.c diff --git a/src/pcm_pack.h b/src/pcm/pcm_pack.h index f3184b403..f3184b403 100644 --- a/src/pcm_pack.h +++ b/src/pcm/pcm_pack.h diff --git a/src/pcm_buffer.c b/src/pcm_buffer.c deleted file mode 100644 index 4b1eb875a..000000000 --- a/src/pcm_buffer.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_buffer.h" -#include "poison.h" - -/** - * Align the specified size to the next 8k boundary. - */ -G_GNUC_CONST -static size_t -align_8k(size_t size) -{ - return ((size - 1) | 0x1fff) + 1; -} - -void * -pcm_buffer_get(struct pcm_buffer *buffer, size_t size) -{ - assert(buffer != NULL); - - if (size == 0) - /* never return NULL, because NULL would be assumed to - be an error condition */ - size = 1; - - if (buffer->size < size) { - /* free the old buffer */ - g_free(buffer->buffer); - - buffer->size = align_8k(size); - buffer->buffer = g_malloc(buffer->size); - } else { - /* discard old buffer contents */ - poison_undefined(buffer->buffer, buffer->size); - } - - assert(buffer->size >= size); - - return buffer->buffer; -} diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h deleted file mode 100644 index 4502976f6..000000000 --- a/src/pcm_buffer.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_BUFFER_H -#define PCM_BUFFER_H - -#include "check.h" - -#include <glib.h> - -#include <assert.h> - -/** - * Manager for a temporary buffer which grows as needed. We could - * allocate a new buffer every time pcm_convert() is called, but that - * would put too much stress on the allocator. - */ -struct pcm_buffer { - void *buffer; - - size_t size; -}; - -/** - * Initialize the buffer, but don't allocate anything yet. - */ -static inline void -pcm_buffer_init(struct pcm_buffer *buffer) -{ - assert(buffer != NULL); - - buffer->buffer = NULL; - buffer->size = 0; -} - -/** - * Free resources. This function may be called more than once. - */ -static inline void -pcm_buffer_deinit(struct pcm_buffer *buffer) -{ - assert(buffer != NULL); - - g_free(buffer->buffer); - - buffer->buffer = NULL; -} - -/** - * Get the buffer, and guarantee a minimum size. This buffer becomes - * invalid with the next pcm_buffer_get() call. - * - * This function will never return NULL, even if size is zero, because - * the PCM library uses the NULL return value to signal "error". An - * empty destination buffer is not always an error. - */ -G_GNUC_MALLOC -void * -pcm_buffer_get(struct pcm_buffer *buffer, size_t size); - -#endif diff --git a/src/pcm_channels.c b/src/pcm_channels.c deleted file mode 100644 index ec2bd69a5..000000000 --- a/src/pcm_channels.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_channels.h" -#include "pcm_buffer.h" -#include "pcm_utils.h" - -#include <assert.h> - -static void -pcm_convert_channels_16_1_to_2(int16_t *restrict dest, - const int16_t *restrict src, - const int16_t *restrict src_end) -{ - while (src < src_end) { - int16_t value = *src++; - - *dest++ = value; - *dest++ = value; - } -} - -static void -pcm_convert_channels_16_2_to_1(int16_t *restrict dest, - const int16_t *restrict src, - const int16_t *restrict src_end) -{ - while (src < src_end) { - int32_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_16_n_to_2(int16_t *restrict dest, - unsigned src_channels, - const int16_t *restrict src, - const int16_t *restrict src_end) -{ - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int16_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int)src_channels; - - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } -} - -const int16_t * -pcm_convert_channels_16(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int16_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int16_t *dest = pcm_buffer_get(buffer, dest_size); - const int16_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_16_1_to_2(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_16_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_16_n_to_2(dest, src_channels, src, - src_end); - else - return NULL; - - return dest; -} - -static void -pcm_convert_channels_24_1_to_2(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int32_t value = *src++; - - *dest++ = value; - *dest++ = value; - } -} - -static void -pcm_convert_channels_24_2_to_1(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int32_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_24_n_to_2(int32_t *restrict dest, - unsigned src_channels, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int)src_channels; - - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } -} - -const int32_t * -pcm_convert_channels_24(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int32_t *dest = pcm_buffer_get(buffer, dest_size); - const int32_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_24_1_to_2(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_24_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_24_n_to_2(dest, src_channels, src, - src_end); - else - return NULL; - - return dest; -} - -static void -pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src, - const int32_t *src_end) -{ - pcm_convert_channels_24_1_to_2(dest, src, src_end); -} - -static void -pcm_convert_channels_32_2_to_1(int32_t *restrict dest, - const int32_t *restrict src, - const int32_t *restrict src_end) -{ - while (src < src_end) { - int64_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_32_n_to_2(int32_t *dest, - unsigned src_channels, const int32_t *src, - const int32_t *src_end) -{ - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int64_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int64_t)src_channels; - - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } -} - -const int32_t * -pcm_convert_channels_32(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int32_t *dest = pcm_buffer_get(buffer, dest_size); - const int32_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_32_1_to_2(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_32_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_32_n_to_2(dest, src_channels, src, - src_end); - else - return NULL; - - return dest; -} diff --git a/src/pcm_channels.h b/src/pcm_channels.h deleted file mode 100644 index 1e4a0991f..000000000 --- a/src/pcm_channels.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_CHANNELS_H -#define MPD_PCM_CHANNELS_H - -#include <stdint.h> -#include <stddef.h> - -struct pcm_buffer; - -/** - * Changes the number of channels in 16 bit PCM data. - * - * @param buffer the destination pcm_buffer object - * @param dest_channels the number of channels requested - * @param src_channels the number of channels in the source buffer - * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int16_t * -pcm_convert_channels_16(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int16_t *src, - size_t src_size, size_t *dest_size_r); - -/** - * Changes the number of channels in 24 bit PCM data (aligned at 32 - * bit boundaries). - * - * @param buffer the destination pcm_buffer object - * @param dest_channels the number of channels requested - * @param src_channels the number of channels in the source buffer - * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_channels_24(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r); - -/** - * Changes the number of channels in 32 bit PCM data. - * - * @param buffer the destination pcm_buffer object - * @param dest_channels the number of channels requested - * @param src_channels the number of channels in the source buffer - * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_channels_32(struct pcm_buffer *buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r); - -#endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c deleted file mode 100644 index 63f9a1b98..000000000 --- a/src/pcm_convert.c +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_convert.h" -#include "pcm_channels.h" -#include "pcm_format.h" -#include "pcm_pack.h" -#include "audio_format.h" -#include "glib_compat.h" - -#include <assert.h> -#include <string.h> -#include <math.h> -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -void pcm_convert_init(struct pcm_convert_state *state) -{ - memset(state, 0, sizeof(*state)); - - pcm_dsd_init(&state->dsd); - pcm_resample_init(&state->resample); - pcm_dither_24_init(&state->dither); - - pcm_buffer_init(&state->format_buffer); - pcm_buffer_init(&state->channels_buffer); -} - -void pcm_convert_deinit(struct pcm_convert_state *state) -{ - pcm_dsd_deinit(&state->dsd); - pcm_resample_deinit(&state->resample); - - pcm_buffer_deinit(&state->format_buffer); - pcm_buffer_deinit(&state->channels_buffer); -} - -void -pcm_convert_reset(struct pcm_convert_state *state) -{ - pcm_dsd_reset(&state->dsd); - pcm_resample_reset(&state->resample); -} - -static const void * -pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format, - uint8_t dest_channels, - uint8_t src_channels, const void *src, - size_t src_size, size_t *dest_size_r, - GError **error_r) -{ - const void *dest = NULL; - - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_S8: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - g_set_error(error_r, pcm_convert_quark(), 0, - "Channel conversion not implemented for format '%s'", - sample_format_to_string(format)); - return NULL; - - case SAMPLE_FORMAT_S16: - dest = pcm_convert_channels_16(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S24_P32: - dest = pcm_convert_channels_24(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - - case SAMPLE_FORMAT_S32: - dest = pcm_convert_channels_32(buffer, dest_channels, - src_channels, src, - src_size, dest_size_r); - break; - } - - if (dest == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %u to %u channels " - "is not implemented", - src_channels, dest_channels); - return NULL; - } - - return dest; -} - -static const int16_t * -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, - GError **error_r) -{ - const int16_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S16); - - buf = pcm_convert_to_16(&state->format_buffer, &state->dither, - src_format->format, src_buffer, src_size, - &len); - 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 == 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) { - buf = pcm_resample_16(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return NULL; - } - - *dest_size_r = len; - return buf; -} - -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, - GError **error_r) -{ - const int32_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S24_P32); - - buf = pcm_convert_to_24(&state->format_buffer, src_format->format, - src_buffer, src_size, &len); - 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 == 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) { - buf = pcm_resample_24(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return NULL; - } - - *dest_size_r = len; - return buf; -} - -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, - GError **error_r) -{ - const int32_t *buf; - size_t len; - - assert(dest_format->format == SAMPLE_FORMAT_S32); - - buf = pcm_convert_to_32(&state->format_buffer, src_format->format, - src_buffer, src_size, &len); - if (buf == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to 32 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 == 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) { - buf = pcm_resample_32(&state->resample, - dest_format->channels, - src_format->sample_rate, buf, len, - dest_format->sample_rate, &len, - error_r); - if (buf == NULL) - return buf; - } - - *dest_size_r = len; - return buf; -} - -static const float * -pcm_convert_float(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, size_t *dest_size_r, - GError **error_r) -{ - const float *buffer = src_buffer; - size_t size = src_size; - - assert(dest_format->format == SAMPLE_FORMAT_FLOAT); - - /* convert channels first, hoping the source format is - supported (float is not) */ - - if (dest_format->channels != src_format->channels) { - buffer = pcm_convert_channels(&state->channels_buffer, - src_format->format, - dest_format->channels, - src_format->channels, - buffer, size, &size, error_r); - if (buffer == NULL) - return NULL; - } - - /* convert to float now */ - - buffer = pcm_convert_to_float(&state->format_buffer, - src_format->format, - buffer, size, &size); - if (buffer == NULL) { - g_set_error(error_r, pcm_convert_quark(), 0, - "Conversion from %s to float is not implemented", - sample_format_to_string(src_format->format)); - return NULL; - } - - /* resample with float, because this is the best format for - libsamplerate */ - - if (src_format->sample_rate != dest_format->sample_rate) { - buffer = pcm_resample_float(&state->resample, - dest_format->channels, - src_format->sample_rate, - buffer, size, - dest_format->sample_rate, &size, - error_r); - if (buffer == NULL) - return NULL; - } - - *dest_size_r = size; - return buffer; -} - -const void * -pcm_convert(struct pcm_convert_state *state, - const struct audio_format *src_format, - const void *src, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r, - GError **error_r) -{ - struct audio_format float_format; - if (src_format->format == SAMPLE_FORMAT_DSD) { - size_t f_size; - const float *f = pcm_dsd_to_float(&state->dsd, - src_format->channels, - false, src, src_size, - &f_size); - if (f == NULL) { - g_set_error_literal(error_r, pcm_convert_quark(), 0, - "DSD to PCM conversion failed"); - return NULL; - } - - float_format = *src_format; - float_format.format = SAMPLE_FORMAT_FLOAT; - - src_format = &float_format; - src = f; - src_size = f_size; - } - - switch (dest_format->format) { - case SAMPLE_FORMAT_S16: - return pcm_convert_16(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_convert_24(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_S32: - return pcm_convert_32(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_convert_float(state, - src_format, src, src_size, - dest_format, dest_size_r, - error_r); - - default: - g_set_error(error_r, pcm_convert_quark(), 0, - "PCM conversion to %s is not implemented", - sample_format_to_string(dest_format->format)); - return NULL; - } -} diff --git a/src/pcm_convert.h b/src/pcm_convert.h deleted file mode 100644 index be11a6e41..000000000 --- a/src/pcm_convert.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_CONVERT_H -#define PCM_CONVERT_H - -#include "pcm_dsd.h" -#include "pcm_resample.h" -#include "pcm_dither.h" -#include "pcm_buffer.h" - -struct audio_format; - -/** - * This object is statically allocated (within another struct), and - * holds buffer allocations and the state for all kinds of PCM - * conversions. - */ -struct pcm_convert_state { - struct pcm_dsd dsd; - - struct pcm_resample_state resample; - - struct pcm_dither dither; - - /** the buffer for converting the sample format */ - struct pcm_buffer format_buffer; - - /** the buffer for converting the channel count */ - struct pcm_buffer channels_buffer; -}; - -static inline GQuark -pcm_convert_quark(void) -{ - return g_quark_from_static_string("pcm_convert"); -} - -/** - * Initializes a pcm_convert_state object. - */ -void pcm_convert_init(struct pcm_convert_state *state); - -/** - * Deinitializes a pcm_convert_state object and frees allocated - * memory. - */ -void pcm_convert_deinit(struct pcm_convert_state *state); - -/** - * Reset the pcm_convert_state object. Use this at the boundary - * between two distinct songs and each time the format changes. - */ -void -pcm_convert_reset(struct pcm_convert_state *state); - -/** - * Converts PCM data between two audio formats. - * - * @param state an initialized pcm_convert_state object - * @param src_format the source audio format - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_format the requested destination audio format - * @param dest_size_r returns the number of bytes of the destination buffer - * @param error_r location to store the error occurring, 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, - GError **error_r); - -#endif diff --git a/src/pcm_dither.c b/src/pcm_dither.c deleted file mode 100644 index 4811946c8..000000000 --- a/src/pcm_dither.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_dither.h" -#include "pcm_prng.h" - -static int16_t -pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither) -{ - int32_t output, rnd; - - enum { - from_bits = 24, - to_bits = 16, - scale_bits = from_bits - to_bits, - round = 1 << (scale_bits - 1), - mask = (1 << scale_bits) - 1, - ONE = 1 << (from_bits - 1), - MIN = -ONE, - MAX = ONE - 1 - }; - - sample += dither->error[0] - dither->error[1] + dither->error[2]; - - dither->error[2] = dither->error[1]; - dither->error[1] = dither->error[0] / 2; - - /* round */ - output = sample + round; - - rnd = pcm_prng(dither->random); - output += (rnd & mask) - (dither->random & mask); - - dither->random = rnd; - - /* clip */ - if (output > MAX) { - output = MAX; - - if (sample > MAX) - sample = MAX; - } else if (output < MIN) { - output = MIN; - - if (sample < MIN) - sample = MIN; - } - - output &= ~mask; - - dither->error[0] = sample - output; - - return (int16_t)(output >> scale_bits); -} - -void -pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end) -{ - while (src < src_end) - *dest++ = pcm_dither_sample_24_to_16(*src++, dither); -} - -static int16_t -pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither) -{ - return pcm_dither_sample_24_to_16(sample >> 8, dither); -} - -void -pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end) -{ - while (src < src_end) - *dest++ = pcm_dither_sample_32_to_16(*src++, dither); -} diff --git a/src/pcm_dither.h b/src/pcm_dither.h deleted file mode 100644 index 046dea21e..000000000 --- a/src/pcm_dither.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_DITHER_H -#define MPD_PCM_DITHER_H - -#include <stdint.h> - -struct pcm_dither { - int32_t error[3]; - int32_t random; -}; - -static inline void -pcm_dither_24_init(struct pcm_dither *dither) -{ - dither->error[0] = dither->error[1] = dither->error[2] = 0; - dither->random = 0; -} - -void -pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end); - -void -pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, const int32_t *src_end); - -#endif diff --git a/src/pcm_dsd.c b/src/pcm_dsd.c deleted file mode 100644 index 76266b4cc..000000000 --- a/src/pcm_dsd.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_dsd.h" -#include "dsd2pcm/dsd2pcm.h" - -#include <glib.h> -#include <string.h> - -void -pcm_dsd_init(struct pcm_dsd *dsd) -{ - pcm_buffer_init(&dsd->buffer); - - memset(dsd->dsd2pcm, 0, sizeof(dsd->dsd2pcm)); -} - -void -pcm_dsd_deinit(struct pcm_dsd *dsd) -{ - pcm_buffer_deinit(&dsd->buffer); - - for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i) - if (dsd->dsd2pcm[i] != NULL) - dsd2pcm_destroy(dsd->dsd2pcm[i]); -} - -void -pcm_dsd_reset(struct pcm_dsd *dsd) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i) - if (dsd->dsd2pcm[i] != NULL) - dsd2pcm_reset(dsd->dsd2pcm[i]); -} - -const float * -pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst, - const uint8_t *src, size_t src_size, - size_t *dest_size_r) -{ - assert(dsd != NULL); - assert(src != NULL); - assert(src_size > 0); - assert(src_size % channels == 0); - assert(channels <= G_N_ELEMENTS(dsd->dsd2pcm)); - - const unsigned num_samples = src_size; - const unsigned num_frames = src_size / channels; - - float *dest; - const size_t dest_size = num_samples * sizeof(*dest); - *dest_size_r = dest_size; - dest = pcm_buffer_get(&dsd->buffer, dest_size); - - for (unsigned c = 0; c < channels; ++c) { - if (dsd->dsd2pcm[c] == NULL) { - dsd->dsd2pcm[c] = dsd2pcm_init(); - if (dsd->dsd2pcm[c] == NULL) - return NULL; - } - - dsd2pcm_translate(dsd->dsd2pcm[c], num_frames, - src + c, channels, - lsbfirst, dest + c, channels); - } - - return dest; -} diff --git a/src/pcm_dsd.h b/src/pcm_dsd.h deleted file mode 100644 index 85c2455aa..000000000 --- a/src/pcm_dsd.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_DSD_H -#define MPD_PCM_DSD_H - -#include "check.h" -#include "pcm_buffer.h" - -#include <stdbool.h> -#include <stdint.h> - -/** - * Wrapper for the dsd2pcm library. - */ -struct pcm_dsd { - struct pcm_buffer buffer; - - struct dsd2pcm_ctx_s *dsd2pcm[32]; -}; - -void -pcm_dsd_init(struct pcm_dsd *dsd); - -void -pcm_dsd_deinit(struct pcm_dsd *dsd); - -void -pcm_dsd_reset(struct pcm_dsd *dsd); - -const float * -pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst, - const uint8_t *src, size_t src_size, - size_t *dest_size_r); - -#endif diff --git a/src/pcm_dsd_usb.c b/src/pcm_dsd_usb.c deleted file mode 100644 index 4b5e39f39..000000000 --- a/src/pcm_dsd_usb.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_dsd_usb.h" -#include "pcm_buffer.h" -#include "audio_format.h" - -G_GNUC_CONST -static inline uint32_t -pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b) -{ - return 0xff050000 | (a << 8) | b; -} - -G_GNUC_CONST -static inline uint32_t -pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b) -{ - return 0xfffa0000 | (a << 8) | b; -} - - -const uint32_t * -pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels, - const uint8_t *src, size_t src_size, - size_t *dest_size_r) -{ - assert(buffer != NULL); - assert(audio_valid_channel_count(channels)); - assert(src != NULL); - assert(src_size > 0); - assert(src_size % channels == 0); - - const unsigned num_src_samples = src_size; - const unsigned num_src_frames = num_src_samples / channels; - - /* this rounds down and discards the last odd frame; not - elegant, but good enough for now */ - const unsigned num_frames = num_src_frames / 2; - const unsigned num_samples = num_frames * channels; - - const size_t dest_size = num_samples * 4; - *dest_size_r = dest_size; - uint32_t *const dest0 = pcm_buffer_get(buffer, dest_size), - *dest = dest0; - - for (unsigned i = num_frames / 2; i > 0; --i) { - for (unsigned c = channels; c > 0; --c) { - /* each 24 bit sample has 16 DSD sample bits - plus the magic 0x05 marker */ - - *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]); - - /* seek the source pointer to the next - channel */ - ++src; - } - - /* skip the second byte of each channel, because we - have already copied it */ - src += channels; - - for (unsigned c = channels; c > 0; --c) { - /* each 24 bit sample has 16 DSD sample bits - plus the magic 0xfa marker */ - - *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]); - - /* seek the source pointer to the next - channel */ - ++src; - } - - /* skip the second byte of each channel, because we - have already copied it */ - src += channels; - } - - return dest0; -} diff --git a/src/pcm_dsd_usb.h b/src/pcm_dsd_usb.h deleted file mode 100644 index 389358459..000000000 --- a/src/pcm_dsd_usb.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_DSD_USB_H -#define MPD_PCM_DSD_USB_H - -#include "check.h" - -#include <stdbool.h> -#include <stdint.h> -#include <stddef.h> - -struct pcm_buffer; - -/** - * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for - * playback over USB, according to the proposed standard by - * dCS and others: - * http://www.sonore.us/DoP_openStandard_1v1.pdf - */ -const uint32_t * -pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels, - const uint8_t *src, size_t src_size, - size_t *dest_size_r); - -#endif diff --git a/src/pcm_export.c b/src/pcm_export.c deleted file mode 100644 index 144ac71cd..000000000 --- a/src/pcm_export.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_export.h" -#include "pcm_dsd_usb.h" -#include "pcm_pack.h" -#include "util/byte_reverse.h" - -void -pcm_export_init(struct pcm_export_state *state) -{ - pcm_buffer_init(&state->reverse_buffer); - pcm_buffer_init(&state->pack_buffer); - pcm_buffer_init(&state->dsd_buffer); -} - -void pcm_export_deinit(struct pcm_export_state *state) -{ - pcm_buffer_deinit(&state->reverse_buffer); - pcm_buffer_deinit(&state->pack_buffer); - pcm_buffer_deinit(&state->dsd_buffer); -} - -void -pcm_export_open(struct pcm_export_state *state, - enum sample_format sample_format, unsigned channels, - bool dsd_usb, bool shift8, bool pack, bool reverse_endian) -{ - assert(audio_valid_sample_format(sample_format)); - assert(!dsd_usb || audio_valid_channel_count(channels)); - - state->channels = channels; - state->dsd_usb = dsd_usb && sample_format == SAMPLE_FORMAT_DSD; - if (state->dsd_usb) - /* after the conversion to DSD-over-USB, the DSD - samples are stuffed inside fake 24 bit samples */ - sample_format = SAMPLE_FORMAT_S24_P32; - - state->shift8 = shift8 && sample_format == SAMPLE_FORMAT_S24_P32; - state->pack24 = pack && sample_format == SAMPLE_FORMAT_S24_P32; - - assert(!state->shift8 || !state->pack24); - - state->reverse_endian = 0; - if (reverse_endian) { - size_t sample_size = state->pack24 - ? 3 - : sample_format_size(sample_format); - assert(sample_size <= 0xff); - - if (sample_size > 1) - state->reverse_endian = sample_size; - } -} - -size_t -pcm_export_frame_size(const struct pcm_export_state *state, - const struct audio_format *audio_format) -{ - assert(state != NULL); - assert(audio_format != NULL); - - if (state->pack24) - /* packed 24 bit samples (3 bytes per sample) */ - return audio_format->channels * 3; - - if (state->dsd_usb) - /* the DSD-over-USB draft says that DSD 1-bit samples - are enclosed within 24 bit samples, and MPD's - representation of 24 bit is padded to 32 bit (4 - bytes per sample) */ - return audio_format->channels * 4; - - return audio_format_frame_size(audio_format); -} - -const void * -pcm_export(struct pcm_export_state *state, const void *data, size_t size, - size_t *dest_size_r) -{ - if (state->dsd_usb) - data = pcm_dsd_to_usb(&state->dsd_buffer, state->channels, - data, size, &size); - - if (state->pack24) { - assert(size % 4 == 0); - - const size_t num_samples = size / 4; - const size_t dest_size = num_samples * 3; - - const uint8_t *src8 = data, *src_end8 = src8 + size; - uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size); - assert(dest != NULL); - - pcm_pack_24(dest, (const int32_t *)src8, - (const int32_t *)src_end8); - - data = dest; - size = dest_size; - } else if (state->shift8) { - assert(size % 4 == 0); - - const uint8_t *src8 = data, *src_end8 = src8 + size; - const uint32_t *src = (const uint32_t *)src8; - const uint32_t *const src_end = (const uint32_t *)src_end8; - - uint32_t *dest = pcm_buffer_get(&state->pack_buffer, size); - data = dest; - - while (src < src_end) - *dest++ = *src++ << 8; - } - - - if (state->reverse_endian > 0) { - assert(state->reverse_endian >= 2); - - void *dest = pcm_buffer_get(&state->reverse_buffer, size); - assert(dest != NULL); - - const uint8_t *src = data, *src_end = src + size; - reverse_bytes(dest, src, src_end, state->reverse_endian); - - data = dest; - } - - *dest_size_r = size; - return data; -} - -size_t -pcm_export_source_size(const struct pcm_export_state *state, size_t size) -{ - if (state->pack24) - /* 32 bit to 24 bit conversion (4 to 3 bytes) */ - size = (size / 3) * 4; - - if (state->dsd_usb) - /* DSD over USB doubles the transport size */ - size /= 2; - - return size; -} diff --git a/src/pcm_export.h b/src/pcm_export.h deleted file mode 100644 index a7e7c3f68..000000000 --- a/src/pcm_export.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_EXPORT_H -#define PCM_EXPORT_H - -#include "check.h" -#include "pcm_buffer.h" -#include "audio_format.h" - -#include <stdbool.h> - -struct audio_format; - -/** - * An object that handles export of PCM samples to some instance - * outside of MPD. It has a few more options to tweak the binary - * representation which are not supported by the pcm_convert library. - */ -struct pcm_export_state { - /** - * The buffer is used to convert DSD samples to the - * DSD-over-USB format. - * - * @see #dsd_usb - */ - struct pcm_buffer dsd_buffer; - - /** - * The buffer is used to pack samples, removing padding. - * - * @see #pack24 - */ - struct pcm_buffer pack_buffer; - - /** - * The buffer is used to reverse the byte order. - * - * @see #reverse_endian - */ - struct pcm_buffer reverse_buffer; - - /** - * The number of channels. - */ - uint8_t channels; - - /** - * Convert DSD to DSD-over-USB? Input format must be - * SAMPLE_FORMAT_DSD and output format must be - * SAMPLE_FORMAT_S24_P32. - */ - bool dsd_usb; - - /** - * Convert (padded) 24 bit samples to 32 bit by shifting 8 - * bits to the left? - */ - bool shift8; - - /** - * Pack 24 bit samples? - */ - bool pack24; - - /** - * Export the samples in reverse byte order? A non-zero value - * means the option is enabled and represents the size of each - * sample (2 or bigger). - */ - uint8_t reverse_endian; -}; - -/** - * Initialize a #pcm_export_state object. - */ -void -pcm_export_init(struct pcm_export_state *state); - -/** - * Deinitialize a #pcm_export_state object and free allocated memory. - */ -void -pcm_export_deinit(struct pcm_export_state *state); - -/** - * Open the #pcm_export_state object. - * - * There is no "close" method. This function may be called multiple - * times to reuse the object, until pcm_export_deinit() is called. - * - * This function cannot fail. - * - * @param channels the number of channels; ignored unless dsd_usb is set - */ -void -pcm_export_open(struct pcm_export_state *state, - enum sample_format sample_format, unsigned channels, - bool dsd_usb, bool shift8, bool pack, bool reverse_endian); - -/** - * Calculate the size of one output frame. - */ -G_GNUC_PURE -size_t -pcm_export_frame_size(const struct pcm_export_state *state, - const struct audio_format *audio_format); - -/** - * Export a PCM buffer. - * - * @param state an initialized and open pcm_export_state object - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer (may be a pointer to the source buffer) - */ -const void * -pcm_export(struct pcm_export_state *state, const void *src, size_t src_size, - size_t *dest_size_r); - -/** - * Converts the number of consumed bytes from the pcm_export() - * destination buffer to the according number of bytes from the - * pcm_export() source buffer. - */ -G_GNUC_PURE -size_t -pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size); - -#endif diff --git a/src/pcm_format.c b/src/pcm_format.c deleted file mode 100644 index d3ea3acb0..000000000 --- a/src/pcm_format.c +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_format.h" -#include "pcm_dither.h" -#include "pcm_buffer.h" -#include "pcm_pack.h" -#include "pcm_utils.h" - -static void -pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) { - *out++ = *in++ << 8; - } -} - -static void -pcm_convert_24_to_16(struct pcm_dither *dither, - int16_t *out, const int32_t *in, const int32_t *in_end) -{ - pcm_dither_24_to_16(dither, out, in, in_end); -} - -static void -pcm_convert_32_to_16(struct pcm_dither *dither, - int16_t *out, const int32_t *in, const int32_t *in_end) -{ - pcm_dither_32_to_16(dither, out, in, in_end); -} - -static void -pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end) -{ - const unsigned OUT_BITS = 16; - const float factor = 1 << (OUT_BITS - 1); - - while (in < in_end) { - int sample = *in++ * factor; - *out++ = pcm_clamp_16(sample); - } -} - -static int16_t * -pcm_allocate_8_to_16(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int16_t * -pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_16(dither, dest, src, - pcm_end_pointer(src, src_size)); - return dest; -} - -static int16_t * -pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_16(dither, dest, src, - pcm_end_pointer(src, src_size)); - return dest; -} - -static int16_t * -pcm_allocate_float_to_16(struct pcm_buffer *buffer, - const float *src, size_t src_size, - size_t *dest_size_r) -{ - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_float_to_16(dest, src, - pcm_end_pointer(src, src_size)); - return dest; -} - -const int16_t * -pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % sample_format_size(src_format) == 0); - - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_16(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S16: - *dest_size_r = src_size; - return src; - - case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_16(buffer, dither, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_16(buffer, dither, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_16(buffer, src, src_size, - dest_size_r); - } - - return NULL; -} - -static void -pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 16; -} - -static void -pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 8; -} - -static void -pcm_convert_32_to_24(int32_t *restrict out, - const int32_t *restrict in, - const int32_t *restrict in_end) -{ - while (in < in_end) - *out++ = *in++ >> 8; -} - -static void -pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end) -{ - const unsigned OUT_BITS = 24; - const float factor = 1 << (OUT_BITS - 1); - - while (in < in_end) { - int sample = *in++ * factor; - *out++ = pcm_clamp_24(sample); - } -} - -static int32_t * -pcm_allocate_8_to_24(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_16_to_24(struct pcm_buffer *buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_32_to_24(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, size_t *dest_size_r) -{ - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_float_to_24(struct pcm_buffer *buffer, - const float *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -const int32_t * -pcm_convert_to_24(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % sample_format_size(src_format) == 0); - - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_24(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_24(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S24_P32: - *dest_size_r = src_size; - return src; - - case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_24(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_24(buffer, src, src_size, - dest_size_r); - } - - return NULL; -} - -static void -pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 24; -} - -static void -pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 16; -} - -static void -pcm_convert_24_to_32(int32_t *restrict out, - const int32_t *restrict in, - const int32_t *restrict in_end) -{ - while (in < in_end) - *out++ = *in++ << 8; -} - -static int32_t * -pcm_allocate_8_to_32(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_16_to_32(struct pcm_buffer *buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_24p32_to_32(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static int32_t * -pcm_allocate_float_to_32(struct pcm_buffer *buffer, - const float *src, size_t src_size, - size_t *dest_size_r) -{ - /* convert to S24_P32 first */ - int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, - dest_size_r); - - /* convert to 32 bit in-place */ - pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); - return dest; -} - -const int32_t * -pcm_convert_to_32(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % sample_format_size(src_format) == 0); - - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_32(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_32(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_32(buffer, src, src_size, - dest_size_r); - - case SAMPLE_FORMAT_S32: - *dest_size_r = src_size; - return src; - - case SAMPLE_FORMAT_FLOAT: - return pcm_allocate_float_to_32(buffer, src, src_size, - dest_size_r); - } - - return NULL; -} - -static void -pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static void -pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static void -pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end) -{ - enum { in_bits = 24 }; - static const float factor = 2.0f / (1 << in_bits); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static void -pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end) -{ - enum { in_bits = sizeof(*in) * 8 }; - static const float factor = 0.5f / (1 << (in_bits - 2)); - while (in < in_end) - *out++ = (float)*in++ * factor; -} - -static float * -pcm_allocate_8_to_float(struct pcm_buffer *buffer, - const int8_t *src, size_t src_size, - size_t *dest_size_r) -{ - float *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static float * -pcm_allocate_16_to_float(struct pcm_buffer *buffer, - const int16_t *src, size_t src_size, - size_t *dest_size_r) -{ - float *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static float * -pcm_allocate_24p32_to_float(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - float *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -static float * -pcm_allocate_32_to_float(struct pcm_buffer *buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) -{ - *dest_size_r = src_size; - float *dest = pcm_buffer_get(buffer, *dest_size_r); - pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} - -const float * -pcm_convert_to_float(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r) -{ - switch (src_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - break; - - case SAMPLE_FORMAT_S8: - return pcm_allocate_8_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S16: - return pcm_allocate_16_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S24_P32: - return pcm_allocate_24p32_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_S32: - return pcm_allocate_32_to_float(buffer, - src, src_size, dest_size_r); - - case SAMPLE_FORMAT_FLOAT: - *dest_size_r = src_size; - return src; - } - - return NULL; -} diff --git a/src/pcm_format.h b/src/pcm_format.h deleted file mode 100644 index 48bcd0662..000000000 --- a/src/pcm_format.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_FORMAT_H -#define PCM_FORMAT_H - -#include "audio_format.h" - -#include <stdint.h> -#include <stddef.h> - -struct pcm_buffer; -struct pcm_dither; - -/** - * Converts PCM samples to 16 bit. If the source format is 24 bit, - * then dithering is applied. - * - * @param buffer a pcm_buffer object - * @param dither a pcm_dither object for 24-to-16 conversion - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int16_t * -pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -/** - * Converts PCM samples to 24 bit (32 bit alignment). - * - * @param buffer a pcm_buffer object - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_to_24(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -/** - * Converts PCM samples to 32 bit. - * - * @param buffer a pcm_buffer object - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_convert_to_32(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -/** - * Converts PCM samples to 32 bit floating point. - * - * @param buffer a pcm_buffer object - * @param bits the number of in the source buffer - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const float * -pcm_convert_to_float(struct pcm_buffer *buffer, - enum sample_format src_format, const void *src, - size_t src_size, size_t *dest_size_r); - -#endif diff --git a/src/pcm_mix.c b/src/pcm_mix.c deleted file mode 100644 index 6c6d1b4ab..000000000 --- a/src/pcm_mix.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_mix.h" -#include "pcm_volume.h" -#include "pcm_utils.h" -#include "audio_format.h" - -#include <glib.h> - -#include <math.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -static void -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; - int32_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 8); - --num_samples; - } -} - -static void -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; - int32_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 16); - --num_samples; - } -} - -static void -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; - int64_t sample2 = *buffer2++; - - sample1 = ((sample1 * volume1 + sample2 * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer1++ = pcm_range(sample1, 24); - --num_samples; - } -} - -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_float(float *buffer1, const float *buffer2, - unsigned num_samples, float volume1, float volume2) -{ - while (num_samples > 0) { - float sample1 = *buffer1; - float sample2 = *buffer2++; - - sample1 = (sample1 * volume1 + sample2 * volume2); - *buffer1++ = sample1; - --num_samples; - } -} - -static bool -pcm_add_vol(void *buffer1, const void *buffer2, size_t size, - int vol1, int vol2, - enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2, - size, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S16: - pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2, - size / 2, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2, - size / 4, vol1, vol2); - return true; - - case SAMPLE_FORMAT_S32: - pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2, - size / 4, vol1, vol2); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_add_vol_float(buffer1, buffer2, size / 4, - pcm_volume_to_float(vol1), - pcm_volume_to_float(vol2)); - return true; - } - - /* unreachable */ - assert(false); - return false; -} - -static void -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_float(float *buffer1, const float *buffer2, unsigned num_samples) -{ - while (num_samples > 0) { - float sample1 = *buffer1; - float sample2 = *buffer2++; - *buffer1++ = sample1 + sample2; - --num_samples; - } -} - -static bool -pcm_add(void *buffer1, const void *buffer2, size_t size, - enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size); - return true; - - case SAMPLE_FORMAT_S16: - pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - return true; - - case SAMPLE_FORMAT_S32: - pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_add_float(buffer1, buffer2, size / 4); - return true; - } - - /* unreachable */ - assert(false); - return false; -} - -bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, - enum sample_format format, float portion1) -{ - int vol1; - float s; - - /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN - * to signal mixing rather than fading */ - if (isnan(portion1)) - return pcm_add(buffer1, buffer2, size, format); - - 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); - - return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); -} diff --git a/src/pcm_mix.h b/src/pcm_mix.h deleted file mode 100644 index 0e58d01ee..000000000 --- a/src/pcm_mix.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_MIX_H -#define PCM_MIX_H - -#include "audio_format.h" - -#include <stdbool.h> -#include <stddef.h> - -/* - * Linearly mixes two PCM buffers. Both must have the same length and - * the same audio format. The formula is: - * - * s1 := s1 * portion1 + s2 * (1 - portion1) - * - * @param buffer1 the first PCM buffer, and the destination buffer - * @param buffer2 the second PCM buffer - * @param size the size of both buffers in bytes - * @param format the sample format of both buffers - * @param portion1 a number between 0.0 and 1.0 specifying the portion - * of the first buffer in the mix; portion2 = (1.0 - portion1). The value - * NaN is used by the MixRamp code to specify that simple addition is required. - * - * @return true on success, false if the format is not supported - */ -G_GNUC_WARN_UNUSED_RESULT -bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, - enum sample_format format, float portion1); - -#endif diff --git a/src/pcm_prng.h b/src/pcm_prng.h deleted file mode 100644 index 457ba4b66..000000000 --- a/src/pcm_prng.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_PRNG_H -#define PCM_PRNG_H - -/** - * A very simple linear congruential PRNG. It's good enough for PCM - * dithering. - */ -static unsigned long -pcm_prng(unsigned long state) -{ - return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; -} - -#endif diff --git a/src/pcm_resample.c b/src/pcm_resample.c deleted file mode 100644 index 4bc057a7e..000000000 --- a/src/pcm_resample.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_resample_internal.h" - -#ifdef HAVE_LIBSAMPLERATE -#include "conf.h" -#endif - -#include <string.h> - -#ifdef HAVE_LIBSAMPLERATE -static bool lsr_enabled; -#endif - -#ifdef HAVE_LIBSAMPLERATE -static bool -pcm_resample_lsr_enabled(void) -{ - return lsr_enabled; -} -#endif - -bool -pcm_resample_global_init(GError **error_r) -{ -#ifdef HAVE_LIBSAMPLERATE - const char *converter = - config_get_string(CONF_SAMPLERATE_CONVERTER, ""); - - lsr_enabled = strcmp(converter, "internal") != 0; - if (lsr_enabled) - return pcm_resample_lsr_global_init(converter, error_r); - else - return true; -#else - (void)error_r; - return true; -#endif -} - -void pcm_resample_init(struct pcm_resample_state *state) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - pcm_resample_lsr_init(state); - else -#endif - pcm_resample_fallback_init(state); -} - -void pcm_resample_deinit(struct pcm_resample_state *state) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - pcm_resample_lsr_deinit(state); - else -#endif - pcm_resample_fallback_deinit(state); -} - -void -pcm_resample_reset(struct pcm_resample_state *state) -{ -#ifdef HAVE_LIBSAMPLERATE - pcm_resample_lsr_reset(state); -#else - (void)state; -#endif -} - -const float * -pcm_resample_float(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - return pcm_resample_lsr_float(state, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error_r); -#else - (void)error_r; -#endif - - /* sizeof(float)==sizeof(int32_t); the fallback resampler does - not do any math on the sample values, so this hack is - possible: */ - return (const float *) - pcm_resample_fallback_32(state, channels, - src_rate, (const int32_t *)src_buffer, - src_size, - dest_rate, dest_size_r); -} - -const int16_t * -pcm_resample_16(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ -#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, - error_r); -#else - (void)error_r; -#endif - - return pcm_resample_fallback_16(state, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r); -} - -const int32_t * -pcm_resample_32(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ -#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, - error_r); -#else - (void)error_r; -#endif - - return pcm_resample_fallback_32(state, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r); -} diff --git a/src/pcm_resample.h b/src/pcm_resample.h deleted file mode 100644 index a49a24142..000000000 --- a/src/pcm_resample.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_RESAMPLE_H -#define MPD_PCM_RESAMPLE_H - -#include "check.h" -#include "pcm_buffer.h" - -#include <stdint.h> -#include <stddef.h> -#include <stdbool.h> - -#ifdef HAVE_LIBSAMPLERATE -#include <samplerate.h> -#endif - -/** - * This object is statically allocated (within another struct), and - * holds buffer allocations and the state for the resampler. - */ -struct pcm_resample_state { -#ifdef HAVE_LIBSAMPLERATE - SRC_STATE *state; - SRC_DATA data; - - struct pcm_buffer in, out; - - struct { - unsigned src_rate; - unsigned dest_rate; - unsigned channels; - } prev; - - int error; -#endif - - struct pcm_buffer buffer; -}; - -bool -pcm_resample_global_init(GError **error_r); - -/** - * Initializes a pcm_resample_state object. - */ -void pcm_resample_init(struct pcm_resample_state *state); - -/** - * Deinitializes a pcm_resample_state object and frees allocated - * memory. - */ -void pcm_resample_deinit(struct pcm_resample_state *state); - -/** - * @see pcm_convert_reset() - */ -void -pcm_resample_reset(struct pcm_resample_state *state); - -/** - * Resamples 32 bit float data. - * - * @param state an initialized pcm_resample_state object - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const float * -pcm_resample_float(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r); - -/** - * Resamples 16 bit PCM data. - * - * @param state an initialized pcm_resample_state object - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int16_t * -pcm_resample_16(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r); - -/** - * Resamples 32 bit PCM data. - * - * @param state an initialized pcm_resample_state object - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -const int32_t * -pcm_resample_32(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r); - -/** - * Resamples 24 bit PCM data. - * - * @param state an initialized pcm_resample_state object - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ -static inline const int32_t * -pcm_resample_24(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ - /* 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, error_r); -} - -#endif diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c deleted file mode 100644 index 1d1dfdf59..000000000 --- a/src/pcm_resample_fallback.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_resample_internal.h" - -#include <assert.h> - -void -pcm_resample_fallback_init(struct pcm_resample_state *state) -{ - pcm_buffer_init(&state->buffer); -} - -void -pcm_resample_fallback_deinit(struct pcm_resample_state *state) -{ - pcm_buffer_deinit(&state->buffer); -} - -/* resampling code blatantly ripped from ESD */ -const int16_t * -pcm_resample_fallback_16(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) -{ - unsigned src_pos, dest_pos = 0; - unsigned src_frames = src_size / channels / sizeof(*src_buffer); - unsigned dest_frames = - (src_frames * dest_rate + src_rate - 1) / src_rate; - unsigned dest_samples = dest_frames * channels; - size_t dest_size = dest_samples * sizeof(*src_buffer); - int16_t *dest_buffer = pcm_buffer_get(&state->buffer, dest_size); - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - switch (channels) { - case 1: - while (dest_pos < dest_samples) { - src_pos = dest_pos * src_rate / dest_rate; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - } - break; - case 2: - while (dest_pos < dest_samples) { - src_pos = dest_pos * src_rate / dest_rate; - src_pos &= ~1; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - dest_buffer[dest_pos++] = src_buffer[src_pos + 1]; - } - break; - } - - *dest_size_r = dest_size; - return dest_buffer; -} - -const int32_t * -pcm_resample_fallback_32(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) -{ - unsigned src_pos, dest_pos = 0; - unsigned src_frames = src_size / channels / sizeof(*src_buffer); - unsigned dest_frames = - (src_frames * dest_rate + src_rate - 1) / src_rate; - unsigned dest_samples = dest_frames * channels; - size_t dest_size = dest_samples * sizeof(*src_buffer); - int32_t *dest_buffer = pcm_buffer_get(&state->buffer, dest_size); - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - switch (channels) { - case 1: - while (dest_pos < dest_samples) { - src_pos = dest_pos * src_rate / dest_rate; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - } - break; - case 2: - while (dest_pos < dest_samples) { - src_pos = dest_pos * src_rate / dest_rate; - src_pos &= ~1; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - dest_buffer[dest_pos++] = src_buffer[src_pos + 1]; - } - break; - } - - *dest_size_r = dest_size; - return dest_buffer; -} diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h deleted file mode 100644 index a0e108d4b..000000000 --- a/src/pcm_resample_internal.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal declarations for the pcm_resample library. The "internal" - * resampler is called "fallback" in the MPD source, so the file name - * of this header is somewhat unrelated to it. - */ - -#ifndef MPD_PCM_RESAMPLE_INTERNAL_H -#define MPD_PCM_RESAMPLE_INTERNAL_H - -#include "check.h" -#include "pcm_resample.h" - -#ifdef HAVE_LIBSAMPLERATE - -bool -pcm_resample_lsr_global_init(const char *converter, GError **error_r); - -void -pcm_resample_lsr_init(struct pcm_resample_state *state); - -void -pcm_resample_lsr_deinit(struct pcm_resample_state *state); - -void -pcm_resample_lsr_reset(struct pcm_resample_state *state); - -const float * -pcm_resample_lsr_float(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r); - -const int16_t * -pcm_resample_lsr_16(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r); - -const int32_t * -pcm_resample_lsr_32(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, - G_GNUC_UNUSED size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r); - -#endif - -void -pcm_resample_fallback_init(struct pcm_resample_state *state); - -void -pcm_resample_fallback_deinit(struct pcm_resample_state *state); - -const int16_t * -pcm_resample_fallback_16(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); - -const int32_t * -pcm_resample_fallback_32(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, - G_GNUC_UNUSED size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); - -#endif diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c deleted file mode 100644 index f957e5155..000000000 --- a/src/pcm_resample_libsamplerate.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_resample_internal.h" -#include "conf.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - -static int lsr_converter = SRC_SINC_FASTEST; - -static inline GQuark -libsamplerate_quark(void) -{ - return g_quark_from_static_string("libsamplerate"); -} - -static bool -lsr_parse_converter(const char *s) -{ - assert(s != NULL); - - if (*s == 0) - return true; - - char *endptr; - long l = strtol(s, &endptr, 10); - if (*endptr == 0 && src_get_name(l) != NULL) { - lsr_converter = l; - return true; - } - - size_t length = strlen(s); - for (int i = 0;; ++i) { - const char *name = src_get_name(i); - if (name == NULL) - break; - - if (g_ascii_strncasecmp(s, name, length) == 0) { - lsr_converter = i; - return true; - } - } - - return false; -} - -bool -pcm_resample_lsr_global_init(const char *converter, GError **error_r) -{ - if (!lsr_parse_converter(converter)) { - g_set_error(error_r, libsamplerate_quark(), 0, - "unknown samplerate converter '%s'", converter); - return false; - } - - g_debug("libsamplerate converter '%s'", - src_get_name(lsr_converter)); - - return true; -} - -void -pcm_resample_lsr_init(struct pcm_resample_state *state) -{ - memset(state, 0, sizeof(*state)); - - pcm_buffer_init(&state->in); - pcm_buffer_init(&state->out); - pcm_buffer_init(&state->buffer); -} - -void -pcm_resample_lsr_deinit(struct pcm_resample_state *state) -{ - if (state->state != NULL) - state->state = src_delete(state->state); - - pcm_buffer_deinit(&state->in); - pcm_buffer_deinit(&state->out); - pcm_buffer_deinit(&state->buffer); -} - -void -pcm_resample_lsr_reset(struct pcm_resample_state *state) -{ - if (state->state != NULL) - src_reset(state->state); -} - -static bool -pcm_resample_set(struct pcm_resample_state *state, - unsigned channels, unsigned src_rate, unsigned dest_rate, - GError **error_r) -{ - int error; - SRC_DATA *data = &state->data; - - /* (re)set the state/ratio if the in or out format changed */ - if (channels == state->prev.channels && - src_rate == state->prev.src_rate && - dest_rate == state->prev.dest_rate) - return true; - - state->error = 0; - state->prev.channels = channels; - state->prev.src_rate = src_rate; - state->prev.dest_rate = dest_rate; - - if (state->state) - state->state = src_delete(state->state); - - state->state = src_new(lsr_converter, channels, &error); - if (!state->state) { - g_set_error(error_r, libsamplerate_quark(), state->error, - "libsamplerate initialization has failed: %s", - 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; -} - -static bool -lsr_process(struct pcm_resample_state *state, GError **error_r) -{ - if (state->error == 0) - state->error = src_process(state->state, &state->data); - if (state->error) { - g_set_error(error_r, libsamplerate_quark(), state->error, - "libsamplerate has failed: %s", - src_strerror(state->error)); - return false; - } - - return true; -} - -static float * -deconst_float_buffer(const float *in) -{ - union { - const float *in; - float *out; - } u = { .in = in }; - return u.out; -} - -const float * -pcm_resample_lsr_float(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r)) - return NULL; - - SRC_DATA *data = &state->data; - data->input_frames = src_size / sizeof(*src_buffer) / channels; - data->data_in = deconst_float_buffer(src_buffer); - - data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; - size_t data_out_size = data->output_frames * sizeof(float) * channels; - data->data_out = pcm_buffer_get(&state->out, data_out_size); - - if (!lsr_process(state, error_r)) - return NULL; - - *dest_size_r = data->output_frames_gen * - sizeof(*data->data_out) * channels; - return data->data_out; -} - -const int16_t * -pcm_resample_lsr_16(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ - bool success; - SRC_DATA *data = &state->data; - size_t data_in_size; - size_t data_out_size; - int16_t *dest_buffer; - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - success = pcm_resample_set(state, channels, src_rate, dest_rate, - error_r); - if (!success) - return NULL; - - data->input_frames = src_size / sizeof(*src_buffer) / channels; - data_in_size = data->input_frames * sizeof(float) * channels; - data->data_in = pcm_buffer_get(&state->in, data_in_size); - - data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; - data_out_size = data->output_frames * sizeof(float) * channels; - data->data_out = pcm_buffer_get(&state->out, data_out_size); - - src_short_to_float_array(src_buffer, data->data_in, - data->input_frames * channels); - - if (!lsr_process(state, error_r)) - return NULL; - - *dest_size_r = data->output_frames_gen * - sizeof(*dest_buffer) * channels; - dest_buffer = pcm_buffer_get(&state->buffer, *dest_size_r); - src_float_to_short_array(data->data_out, dest_buffer, - data->output_frames_gen * channels); - - return dest_buffer; -} - -#ifdef HAVE_LIBSAMPLERATE_NOINT - -/* libsamplerate introduced these functions in v0.1.3 */ - -static void -src_int_to_float_array(const int *in, float *out, int len) -{ - while (len-- > 0) - *out++ = *in++ / (float)(1 << (24 - 1)); -} - -static void -src_float_to_int_array (const float *in, int *out, int len) -{ - while (len-- > 0) - *out++ = *in++ * (float)(1 << (24 - 1)); -} - -#endif - -const int32_t * -pcm_resample_lsr_32(struct pcm_resample_state *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - GError **error_r) -{ - bool success; - SRC_DATA *data = &state->data; - size_t data_in_size; - size_t data_out_size; - int32_t *dest_buffer; - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - success = pcm_resample_set(state, channels, src_rate, dest_rate, - error_r); - if (!success) - return NULL; - - data->input_frames = src_size / sizeof(*src_buffer) / channels; - data_in_size = data->input_frames * sizeof(float) * channels; - data->data_in = pcm_buffer_get(&state->in, data_in_size); - - data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; - data_out_size = data->output_frames * sizeof(float) * channels; - data->data_out = pcm_buffer_get(&state->out, data_out_size); - - src_int_to_float_array(src_buffer, data->data_in, - data->input_frames * channels); - - if (!lsr_process(state, error_r)) - return NULL; - - *dest_size_r = data->output_frames_gen * - sizeof(*dest_buffer) * channels; - dest_buffer = pcm_buffer_get(&state->buffer, *dest_size_r); - src_float_to_int_array(data->data_out, dest_buffer, - data->output_frames_gen * channels); - - return dest_buffer; -} diff --git a/src/pcm_utils.h b/src/pcm_utils.h deleted file mode 100644 index 4ad896570..000000000 --- a/src/pcm_utils.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_UTILS_H -#define MPD_PCM_UTILS_H - -#include <glib.h> - -#include <stdint.h> - -/** - * Add a byte count to the specified pointer. This is a utility - * function to convert a source pointer and a byte count to an "end" - * pointer for use in loops. - */ -static inline const void * -pcm_end_pointer(const void *p, size_t size) -{ - return (const char *)p + size; -} - -/** - * Check if the value is within the range of the provided bit size, - * and caps it if necessary. - */ -static inline int32_t -pcm_range(int32_t sample, unsigned bits) -{ - if (G_UNLIKELY(sample < (-1 << (bits - 1)))) - return -1 << (bits - 1); - if (G_UNLIKELY(sample >= (1 << (bits - 1)))) - return (1 << (bits - 1)) - 1; - 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; -} - -G_GNUC_CONST -static inline int16_t -pcm_clamp_16(int x) -{ - static const int32_t MIN_VALUE = G_MININT16; - static const int32_t MAX_VALUE = G_MAXINT16; - - if (G_UNLIKELY(x < MIN_VALUE)) - return MIN_VALUE; - if (G_UNLIKELY(x > MAX_VALUE)) - return MAX_VALUE; - return x; -} - -G_GNUC_CONST -static inline int32_t -pcm_clamp_24(int x) -{ - static const int32_t MIN_VALUE = -(1 << 23); - static const int32_t MAX_VALUE = (1 << 23) - 1; - - if (G_UNLIKELY(x < MIN_VALUE)) - return MIN_VALUE; - if (G_UNLIKELY(x > MAX_VALUE)) - return MAX_VALUE; - return x; -} - -#endif diff --git a/src/pcm_volume.c b/src/pcm_volume.c deleted file mode 100644 index 49c86026f..000000000 --- a/src/pcm_volume.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pcm_volume.h" -#include "pcm_utils.h" -#include "audio_format.h" - -#include <glib.h> - -#include <stdint.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm_volume" - -static void -pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) -{ - while (buffer < end) { - int32_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer++ = pcm_range(sample, 8); - } -} - -static void -pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) -{ - while (buffer < end) { - int32_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer++ = pcm_range(sample, 16); - } -} - -#ifdef __i386__ -/** - * Optimized volume function for i386. Use the EDX:EAX 2*32 bit - * multiplication result instead of emulating 64 bit multiplication. - */ -static inline int32_t -pcm_volume_sample_24(int32_t sample, int32_t volume, G_GNUC_UNUSED int32_t dither) -{ - int32_t result; - - asm(/* edx:eax = sample * volume */ - "imul %2\n" - - /* "add %3, %1\n" dithering disabled for now, because we - have no overflow check - is dithering really important - here? */ - - /* eax = edx:eax / PCM_VOLUME_1 */ - "sal $22, %%edx\n" - "shr $10, %1\n" - "or %%edx, %1\n" - - : "=a"(result) - : "0"(sample), "r"(volume) /* , "r"(dither) */ - : "edx" - ); - - return result; -} -#endif - -static void -pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) -{ - while (buffer < end) { -#ifdef __i386__ - /* assembly version for i386 */ - int32_t sample = *buffer; - - sample = pcm_volume_sample_24(sample, volume, - pcm_volume_dither()); -#else - /* portable version */ - int64_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; -#endif - *buffer++ = pcm_range(sample, 24); - } -} - -static void -pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) -{ - while (buffer < end) { -#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 - } -} - -static void -pcm_volume_change_float(float *buffer, const float *end, float volume) -{ - while (buffer < end) { - float sample = *buffer; - sample *= volume; - *buffer++ = sample; - } -} - -bool -pcm_volume(void *buffer, size_t length, - enum sample_format format, - int volume) -{ - if (volume == PCM_VOLUME_1) - return true; - - if (volume <= 0) { - memset(buffer, 0, length); - return true; - } - - const void *end = pcm_end_pointer(buffer, length); - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: - /* not implemented */ - return false; - - case SAMPLE_FORMAT_S8: - pcm_volume_change_8(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_S16: - pcm_volume_change_16(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_S24_P32: - pcm_volume_change_24(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_S32: - pcm_volume_change_32(buffer, end, volume); - return true; - - case SAMPLE_FORMAT_FLOAT: - pcm_volume_change_float(buffer, end, - pcm_volume_to_float(volume)); - return true; - } - - /* unreachable */ - assert(false); - return false; -} diff --git a/src/pcm_volume.h b/src/pcm_volume.h deleted file mode 100644 index 64e3c7641..000000000 --- a/src/pcm_volume.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PCM_VOLUME_H -#define PCM_VOLUME_H - -#include "pcm_prng.h" -#include "audio_format.h" - -#include <stdint.h> -#include <stdbool.h> - -enum { - /** this value means "100% volume" */ - PCM_VOLUME_1 = 1024, -}; - -struct audio_format; - -/** - * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an - * integer volume value (1000 = 100%). - */ -static inline int -pcm_float_to_volume(float volume) -{ - return volume * PCM_VOLUME_1 + 0.5; -} - -static inline float -pcm_volume_to_float(int volume) -{ - return (float)volume / (float)PCM_VOLUME_1; -} - -/** - * Returns the next volume dithering number, between -511 and +511. - * This number is taken from a global PRNG, see pcm_prng(). - */ -static inline int -pcm_volume_dither(void) -{ - static unsigned long state; - uint32_t r; - - r = state = pcm_prng(state); - - return (r & 511) - ((r >> 9) & 511); -} - -/** - * Adjust the volume of the specified PCM buffer. - * - * @param buffer the PCM buffer - * @param length the length of the PCM buffer - * @param format the sample format of the PCM buffer - * @param volume the volume between 0 and #PCM_VOLUME_1 - * @return true on success, false if the audio format is not supported - */ -bool -pcm_volume(void *buffer, size_t length, - enum sample_format format, - int volume); - -#endif diff --git a/src/permission.c b/src/permission.c deleted file mode 100644 index cd52b9c86..000000000 --- a/src/permission.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "permission.h" -#include "conf.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <stdbool.h> -#include <string.h> - -#define PERMISSION_PASSWORD_CHAR '@' -#define PERMISSION_SEPERATOR "," - -#define PERMISSION_READ_STRING "read" -#define PERMISSION_ADD_STRING "add" -#define PERMISSION_CONTROL_STRING "control" -#define PERMISSION_ADMIN_STRING "admin" - -static GHashTable *permission_passwords; - -static unsigned permission_default; - -static unsigned parsePermissions(const char *string) -{ - unsigned permission = 0; - gchar **tokens; - - if (!string) - return 0; - - tokens = g_strsplit(string, PERMISSION_SEPERATOR, 0); - for (unsigned i = 0; tokens[i] != NULL; ++i) { - char *temp = tokens[i]; - - if (strcmp(temp, PERMISSION_READ_STRING) == 0) { - permission |= PERMISSION_READ; - } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) { - permission |= PERMISSION_ADD; - } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) { - permission |= PERMISSION_CONTROL; - } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { - permission |= PERMISSION_ADMIN; - } else { - MPD_ERROR("unknown permission \"%s\"", temp); - } - } - - g_strfreev(tokens); - - return permission; -} - -void initPermissions(void) -{ - char *password; - unsigned permission; - const struct config_param *param; - - permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); - - permission_default = PERMISSION_READ | PERMISSION_ADD | - PERMISSION_CONTROL | PERMISSION_ADMIN; - - param = config_get_next_param(CONF_PASSWORD, NULL); - - if (param) { - permission_default = 0; - - do { - const char *separator = - strchr(param->value, PERMISSION_PASSWORD_CHAR); - - if (separator == NULL) - MPD_ERROR("\"%c\" not found in password string " - "\"%s\", line %i", - PERMISSION_PASSWORD_CHAR, - param->value, param->line); - - password = g_strndup(param->value, - separator - param->value); - - permission = parsePermissions(separator + 1); - - g_hash_table_replace(permission_passwords, - password, - GINT_TO_POINTER(permission)); - } while ((param = config_get_next_param(CONF_PASSWORD, param))); - } - - param = config_get_param(CONF_DEFAULT_PERMS); - - if (param) - permission_default = parsePermissions(param->value); -} - -int getPermissionFromPassword(char const* password, unsigned* permission) -{ - bool found; - gpointer key, value; - - found = g_hash_table_lookup_extended(permission_passwords, - password, &key, &value); - if (!found) - return -1; - - *permission = GPOINTER_TO_INT(value); - return 0; -} - -void finishPermissions(void) -{ - g_hash_table_destroy(permission_passwords); -} - -unsigned getDefaultPermissions(void) -{ - return permission_default; -} diff --git a/src/permission.h b/src/permission.h deleted file mode 100644 index 6c3771362..000000000 --- a/src/permission.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PERMISSION_H -#define MPD_PERMISSION_H - -#define PERMISSION_NONE 0 -#define PERMISSION_READ 1 -#define PERMISSION_ADD 2 -#define PERMISSION_CONTROL 4 -#define PERMISSION_ADMIN 8 - - -int getPermissionFromPassword(char const* password, unsigned* permission); - -void finishPermissions(void); - -unsigned getDefaultPermissions(void); - -void initPermissions(void); - -#endif diff --git a/src/pipe.c b/src/pipe.c deleted file mode 100644 index d8131432f..000000000 --- a/src/pipe.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "pipe.h" -#include "buffer.h" -#include "chunk.h" - -#include <glib.h> - -#include <assert.h> - -struct music_pipe { - /** the first chunk */ - struct music_chunk *head; - - /** a pointer to the tail of the chunk */ - struct music_chunk **tail_r; - - /** the current number of chunks */ - unsigned size; - - /** a mutex which protects #head and #tail_r */ - GMutex *mutex; - -#ifndef NDEBUG - struct audio_format audio_format; -#endif -}; - -struct music_pipe * -music_pipe_new(void) -{ - struct music_pipe *mp = g_new(struct music_pipe, 1); - - mp->head = NULL; - mp->tail_r = &mp->head; - mp->size = 0; - mp->mutex = g_mutex_new(); - -#ifndef NDEBUG - audio_format_clear(&mp->audio_format); -#endif - - return mp; -} - -void -music_pipe_free(struct music_pipe *mp) -{ - assert(mp->head == NULL); - assert(mp->tail_r == &mp->head); - - g_mutex_free(mp->mutex); - g_free(mp); -} - -#ifndef NDEBUG - -bool -music_pipe_check_format(const struct music_pipe *pipe, - const struct audio_format *audio_format) -{ - assert(pipe != NULL); - assert(audio_format != NULL); - - return !audio_format_defined(&pipe->audio_format) || - audio_format_equals(&pipe->audio_format, audio_format); -} - -bool -music_pipe_contains(const struct music_pipe *mp, - const struct music_chunk *chunk) -{ - g_mutex_lock(mp->mutex); - - for (const struct music_chunk *i = mp->head; - i != NULL; i = i->next) { - if (i == chunk) { - g_mutex_unlock(mp->mutex); - return true; - } - } - - g_mutex_unlock(mp->mutex); - - return false; -} - -#endif - -const struct music_chunk * -music_pipe_peek(const struct music_pipe *mp) -{ - return mp->head; -} - -struct music_chunk * -music_pipe_shift(struct music_pipe *mp) -{ - struct music_chunk *chunk; - - g_mutex_lock(mp->mutex); - - chunk = mp->head; - if (chunk != NULL) { - assert(!music_chunk_is_empty(chunk)); - - mp->head = chunk->next; - --mp->size; - - if (mp->head == NULL) { - assert(mp->size == 0); - assert(mp->tail_r == &chunk->next); - - mp->tail_r = &mp->head; - } else { - assert(mp->size > 0); - assert(mp->tail_r != &chunk->next); - } - -#ifndef NDEBUG - /* poison the "next" reference */ - chunk->next = (void*)0x01010101; - - if (mp->size == 0) - audio_format_clear(&mp->audio_format); -#endif - } - - g_mutex_unlock(mp->mutex); - - return chunk; -} - -void -music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer) -{ - struct music_chunk *chunk; - - while ((chunk = music_pipe_shift(mp)) != NULL) - music_buffer_return(buffer, chunk); -} - -void -music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) -{ - assert(!music_chunk_is_empty(chunk)); - assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format)); - - g_mutex_lock(mp->mutex); - - assert(mp->size > 0 || !audio_format_defined(&mp->audio_format)); - assert(!audio_format_defined(&mp->audio_format) || - music_chunk_check_format(chunk, &mp->audio_format)); - -#ifndef NDEBUG - if (!audio_format_defined(&mp->audio_format) && chunk->length > 0) - mp->audio_format = chunk->audio_format; -#endif - - chunk->next = NULL; - *mp->tail_r = chunk; - mp->tail_r = &chunk->next; - - ++mp->size; - - g_mutex_unlock(mp->mutex); -} - -unsigned -music_pipe_size(const struct music_pipe *mp) -{ - g_mutex_lock(mp->mutex); - unsigned size = mp->size; - g_mutex_unlock(mp->mutex); - return size; -} diff --git a/src/pipe.h b/src/pipe.h deleted file mode 100644 index 84b9869e0..000000000 --- a/src/pipe.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PIPE_H -#define MPD_PIPE_H - -#include <glib.h> -#include <stdbool.h> - -#ifndef NDEBUG -struct audio_format; -#endif - -struct music_chunk; -struct music_buffer; - -/** - * A queue of #music_chunk objects. One party appends chunks at the - * tail, and the other consumes them from the head. - */ -struct music_pipe; - -/** - * Creates a new #music_pipe object. It is empty. - */ -G_GNUC_MALLOC -struct music_pipe * -music_pipe_new(void); - -/** - * Frees the object. It must be empty now. - */ -void -music_pipe_free(struct music_pipe *mp); - -#ifndef NDEBUG - -/** - * Checks if the audio format if the chunk is equal to the specified - * audio_format. - */ -bool -music_pipe_check_format(const struct music_pipe *pipe, - const struct audio_format *audio_format); - -/** - * Checks if the specified chunk is enqueued in the music pipe. - */ -bool -music_pipe_contains(const struct music_pipe *mp, - const struct music_chunk *chunk); - -#endif - -/** - * Returns the first #music_chunk from the pipe. Returns NULL if the - * pipe is empty. - */ -G_GNUC_PURE -const struct music_chunk * -music_pipe_peek(const struct music_pipe *mp); - -/** - * Removes the first chunk from the head, and returns it. - */ -struct music_chunk * -music_pipe_shift(struct music_pipe *mp); - -/** - * Clears the whole pipe and returns the chunks to the buffer. - * - * @param buffer the buffer object to return the chunks to - */ -void -music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer); - -/** - * Pushes a chunk to the tail of the pipe. - */ -void -music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); - -/** - * Returns the number of chunks currently in this pipe. - */ -G_GNUC_PURE -unsigned -music_pipe_size(const struct music_pipe *mp); - -G_GNUC_PURE -static inline bool -music_pipe_empty(const struct music_pipe *mp) -{ - return music_pipe_size(mp) == 0; -} - -#endif diff --git a/src/player_control.c b/src/player_control.c deleted file mode 100644 index b18639087..000000000 --- a/src/player_control.c +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "player_control.h" -#include "decoder_control.h" -#include "path.h" -#include "log.h" -#include "tag.h" -#include "song.h" -#include "idle.h" -#include "pcm_volume.h" -#include "main.h" - -#include <assert.h> -#include <stdio.h> -#include <math.h> - -static void -pc_enqueue_song_locked(struct player_control *pc, struct song *song); - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) -{ - struct player_control *pc = g_new0(struct player_control, 1); - - pc->buffer_chunks = buffer_chunks; - pc->buffered_before_play = buffered_before_play; - - pc->mutex = g_mutex_new(); - pc->cond = g_cond_new(); - - pc->command = PLAYER_COMMAND_NONE; - pc->error = PLAYER_ERROR_NOERROR; - pc->state = PLAYER_STATE_STOP; - pc->cross_fade_seconds = 0; - pc->mixramp_db = 0; - pc->mixramp_delay_seconds = nanf(""); - - return pc; -} - -void -pc_free(struct player_control *pc) -{ - g_cond_free(pc->cond); - g_mutex_free(pc->mutex); - g_free(pc); -} - -void -player_wait_decoder(struct player_control *pc, struct decoder_control *dc) -{ - assert(pc != NULL); - assert(dc != NULL); - assert(dc->client_cond == pc->cond); - - /* during this function, the decoder lock is held, because - we're waiting for the decoder thread */ - g_cond_wait(pc->cond, dc->mutex); -} - -void -pc_song_deleted(struct player_control *pc, const struct song *song) -{ - if (pc->errored_song == song) { - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - } -} - -static void -player_command_wait_locked(struct player_control *pc) -{ - while (pc->command != PLAYER_COMMAND_NONE) - g_cond_wait(main_cond, pc->mutex); -} - -static void -player_command_locked(struct player_control *pc, enum player_command cmd) -{ - assert(pc->command == PLAYER_COMMAND_NONE); - - pc->command = cmd; - player_signal(pc); - player_command_wait_locked(pc); -} - -static void -player_command(struct player_control *pc, enum player_command cmd) -{ - player_lock(pc); - player_command_locked(pc, cmd); - player_unlock(pc); -} - -void -pc_play(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - - player_lock(pc); - - if (pc->state != PLAYER_STATE_STOP) - player_command_locked(pc, PLAYER_COMMAND_STOP); - - assert(pc->next_song == NULL); - - pc_enqueue_song_locked(pc, song); - - assert(pc->next_song == NULL); - - player_unlock(pc); -} - -void -pc_cancel(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_CANCEL); - assert(pc->next_song == NULL); -} - -void -pc_stop(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO); - assert(pc->next_song == NULL); - - idle_add(IDLE_PLAYER); -} - -void -pc_update_audio(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO); -} - -void -pc_kill(struct player_control *pc) -{ - assert(pc->thread != NULL); - - player_command(pc, PLAYER_COMMAND_EXIT); - g_thread_join(pc->thread); - pc->thread = NULL; - - idle_add(IDLE_PLAYER); -} - -void -pc_pause(struct player_control *pc) -{ - player_lock(pc); - - if (pc->state != PLAYER_STATE_STOP) { - player_command_locked(pc, PLAYER_COMMAND_PAUSE); - idle_add(IDLE_PLAYER); - } - - player_unlock(pc); -} - -static void -pc_pause_locked(struct player_control *pc) -{ - if (pc->state != PLAYER_STATE_STOP) { - player_command_locked(pc, PLAYER_COMMAND_PAUSE); - idle_add(IDLE_PLAYER); - } -} - -void -pc_set_pause(struct player_control *pc, bool pause_flag) -{ - player_lock(pc); - - switch (pc->state) { - case PLAYER_STATE_STOP: - break; - - case PLAYER_STATE_PLAY: - if (pause_flag) - pc_pause_locked(pc); - break; - - case PLAYER_STATE_PAUSE: - if (!pause_flag) - pc_pause_locked(pc); - break; - } - - player_unlock(pc); -} - -void -pc_set_border_pause(struct player_control *pc, bool border_pause) -{ - player_lock(pc); - pc->border_pause = border_pause; - player_unlock(pc); -} - -void -pc_get_status(struct player_control *pc, struct player_status *status) -{ - player_lock(pc); - player_command_locked(pc, PLAYER_COMMAND_REFRESH); - - 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(pc); -} - -enum player_state -pc_get_state(struct player_control *pc) -{ - return pc->state; -} - -void -pc_clear_error(struct player_control *pc) -{ - player_lock(pc); - pc->error = PLAYER_ERROR_NOERROR; - pc->errored_song = NULL; - player_unlock(pc); -} - -enum player_error -pc_get_error(struct player_control *pc) -{ - return pc->error; -} - -static char * -pc_errored_song_uri(struct player_control *pc) -{ - return song_get_uri(pc->errored_song); -} - -char * -pc_get_error_message(struct player_control *pc) -{ - char *error; - char *uri; - - switch (pc->error) { - case PLAYER_ERROR_NOERROR: - return NULL; - - case PLAYER_ERROR_FILENOTFOUND: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); - g_free(uri); - return error; - - case PLAYER_ERROR_FILE: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("problems decoding \"%s\"", uri); - g_free(uri); - return error; - - case PLAYER_ERROR_AUDIO: - return g_strdup("problems opening audio device"); - - case PLAYER_ERROR_SYSTEM: - return g_strdup("system error occurred"); - - case PLAYER_ERROR_UNKTYPE: - uri = pc_errored_song_uri(pc); - error = g_strdup_printf("file type of \"%s\" is unknown", uri); - g_free(uri); - return error; - } - - assert(false); - return NULL; -} - -static void -pc_enqueue_song_locked(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - assert(pc->next_song == NULL); - - pc->next_song = song; - player_command_locked(pc, PLAYER_COMMAND_QUEUE); -} - -void -pc_enqueue_song(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - - player_lock(pc); - pc_enqueue_song_locked(pc, song); - player_unlock(pc); -} - -bool -pc_seek(struct player_control *pc, struct song *song, float seek_time) -{ - assert(song != NULL); - - player_lock(pc); - pc->next_song = song; - pc->seek_where = seek_time; - player_command_locked(pc, PLAYER_COMMAND_SEEK); - player_unlock(pc); - - assert(pc->next_song == NULL); - - idle_add(IDLE_PLAYER); - - return true; -} - -float -pc_get_cross_fade(const struct player_control *pc) -{ - return pc->cross_fade_seconds; -} - -void -pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) -{ - if (cross_fade_seconds < 0) - cross_fade_seconds = 0; - pc->cross_fade_seconds = cross_fade_seconds; - - idle_add(IDLE_OPTIONS); -} - -float -pc_get_mixramp_db(const struct player_control *pc) -{ - return pc->mixramp_db; -} - -void -pc_set_mixramp_db(struct player_control *pc, float mixramp_db) -{ - pc->mixramp_db = mixramp_db; - - idle_add(IDLE_OPTIONS); -} - -float -pc_get_mixramp_delay(const struct player_control *pc) -{ - return pc->mixramp_delay_seconds; -} - -void -pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) -{ - pc->mixramp_delay_seconds = mixramp_delay_seconds; - - idle_add(IDLE_OPTIONS); -} - -double -pc_get_total_play_time(const struct player_control *pc) -{ - return pc->total_play_time; -} diff --git a/src/player_control.h b/src/player_control.h deleted file mode 100644 index a77d31ec5..000000000 --- a/src/player_control.h +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYER_H -#define MPD_PLAYER_H - -#include "audio_format.h" - -#include <glib.h> - -#include <stdint.h> - -struct decoder_control; - -enum player_state { - PLAYER_STATE_STOP = 0, - PLAYER_STATE_PAUSE, - PLAYER_STATE_PLAY -}; - -enum player_command { - PLAYER_COMMAND_NONE = 0, - PLAYER_COMMAND_EXIT, - PLAYER_COMMAND_STOP, - 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, - - /** - * cancel pre-decoding player_control.next_song; if the player - * has already started playing this song, it will completely - * stop - */ - PLAYER_COMMAND_CANCEL, - - /** - * Refresh status information in the #player_control struct, - * e.g. elapsed_time. - */ - PLAYER_COMMAND_REFRESH, -}; - -enum player_error { - PLAYER_ERROR_NOERROR = 0, - PLAYER_ERROR_FILE, - PLAYER_ERROR_AUDIO, - PLAYER_ERROR_SYSTEM, - PLAYER_ERROR_UNKTYPE, - 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; - - unsigned int buffered_before_play; - - /** the handle of the player thread, or NULL if the player - thread isn't running */ - GThread *thread; - - /** - * 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 *next_song; - const struct song *errored_song; - double seek_where; - float cross_fade_seconds; - float mixramp_db; - float mixramp_delay_seconds; - double total_play_time; - - /** - * If this flag is set, then the player will be auto-paused at - * the end of the song, before the next song starts to play. - * - * This is a copy of the queue's "single" flag most of the - * time. - */ - bool border_pause; -}; - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned buffered_before_play); - -void -pc_free(struct player_control *pc); - -/** - * Locks the #player_control object. - */ -static inline void -player_lock(struct player_control *pc) -{ - g_mutex_lock(pc->mutex); -} - -/** - * Unlocks the #player_control object. - */ -static inline void -player_unlock(struct player_control *pc) -{ - 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(struct player_control *pc) -{ - 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 player_control *pc, struct decoder_control *dc); - -/** - * Signals the #player_control object. The object should be locked - * prior to calling this function. - */ -static inline void -player_signal(struct player_control *pc) -{ - g_cond_signal(pc->cond); -} - -/** - * Signals the #player_control object. The object is temporarily - * locked by this function. - */ -static inline void -player_lock_signal(struct player_control *pc) -{ - player_lock(pc); - player_signal(pc); - player_unlock(pc); -} - -/** - * 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. - */ -void -pc_song_deleted(struct player_control *pc, const struct song *song); - -void -pc_play(struct player_control *pc, struct song *song); - -/** - * see PLAYER_COMMAND_CANCEL - */ -void -pc_cancel(struct player_control *pc); - -void -pc_set_pause(struct player_control *pc, bool pause_flag); - -void -pc_pause(struct player_control *pc); - -/** - * Set the player's #border_pause flag. - */ -void -pc_set_border_pause(struct player_control *pc, bool border_pause); - -void -pc_kill(struct player_control *pc); - -void -pc_get_status(struct player_control *pc, struct player_status *status); - -enum player_state -pc_get_state(struct player_control *pc); - -void -pc_clear_error(struct player_control *pc); - -/** - * 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(struct player_control *pc); - -enum player_error -pc_get_error(struct player_control *pc); - -void -pc_stop(struct player_control *pc); - -void -pc_update_audio(struct player_control *pc); - -void -pc_enqueue_song(struct player_control *pc, struct song *song); - -/** - * Makes the player thread seek the specified song to a position. - * - * @return true on success, false on failure (e.g. if MPD isn't - * playing currently) - */ -bool -pc_seek(struct player_control *pc, struct song *song, float seek_time); - -void -pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds); - -float -pc_get_cross_fade(const struct player_control *pc); - -void -pc_set_mixramp_db(struct player_control *pc, float mixramp_db); - -float -pc_get_mixramp_db(const struct player_control *pc); - -void -pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); - -float -pc_get_mixramp_delay(const struct player_control *pc); - -double -pc_get_total_play_time(const struct player_control *pc); - -#endif diff --git a/src/player_thread.c b/src/player_thread.c deleted file mode 100644 index ac0b0579e..000000000 --- a/src/player_thread.c +++ /dev/null @@ -1,1175 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "player_thread.h" -#include "player_control.h" -#include "decoder_control.h" -#include "decoder_thread.h" -#include "output_all.h" -#include "pcm_volume.h" -#include "path.h" -#include "event_pipe.h" -#include "crossfade.h" -#include "song.h" -#include "tag.h" -#include "pipe.h" -#include "chunk.h" -#include "idle.h" -#include "main.h" -#include "buffer.h" -#include "mpd_error.h" - -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "player_thread" - -enum xfade_state { - XFADE_DISABLED = -1, - XFADE_UNKNOWN = 0, - XFADE_ENABLED = 1 -}; - -struct player { - struct player_control *pc; - - struct decoder_control *dc; - - struct music_pipe *pipe; - - /** - * are we waiting for buffered_before_play? - */ - bool buffering; - - /** - * true if the decoder is starting and did not provide data - * yet - */ - bool decoder_starting; - - /** - * is the player paused? - */ - bool paused; - - /** - * is there a new song in pc.next_song? - */ - bool queued; - - /** - * Was any audio output opened successfully? It might have - * failed meanwhile, but was not explicitly closed by the - * player thread. When this flag is unset, some output - * methods must not be called. - */ - bool output_open; - - /** - * the song currently being played - */ - struct song *song; - - /** - * is cross fading enabled? - */ - enum xfade_state xfade; - - /** - * has cross-fading begun? - */ - bool cross_fading; - - /** - * The number of chunks used for crossfading. - */ - unsigned cross_fade_chunks; - - /** - * The tag of the "next" song during cross-fade. It is - * postponed, and sent to the output thread when the new song - * really begins. - */ - struct tag *cross_fade_tag; - - /** - * The current audio format for the audio outputs. - */ - struct audio_format play_audio_format; - - /** - * 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 - * precisely. - */ - float elapsed_time; -}; - -static struct music_buffer *player_buffer; - -static void -player_command_finished_locked(struct player_control *pc) -{ - assert(pc->command != PLAYER_COMMAND_NONE); - - pc->command = PLAYER_COMMAND_NONE; - g_cond_signal(main_cond); -} - -static void -player_command_finished(struct player_control *pc) -{ - player_lock(pc); - player_command_finished_locked(pc); - player_unlock(pc); -} - -/** - * Start the decoder. - * - * Player lock is not held. - */ -static void -player_dc_start(struct player *player, struct music_pipe *pipe) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); - assert(pc->next_song != NULL); - - unsigned start_ms = pc->next_song->start_ms; - if (pc->command == PLAYER_COMMAND_SEEK) - start_ms += (unsigned)(pc->seek_where * 1000); - - dc_start(dc, pc->next_song, - start_ms, pc->next_song->end_ms, - player_buffer, pipe); -} - -/** - * Is the decoder still busy on the same song as the player? - * - * Note: this function does not check if the decoder is already - * finished. - */ -static bool -player_dc_at_current_song(const struct player *player) -{ - assert(player != NULL); - assert(player->pipe != NULL); - - return player->dc->pipe == player->pipe; -} - -/** - * Returns true if the decoder is decoding the next song (or has begun - * decoding it, or has finished doing it), and the player hasn't - * switched to that song yet. - */ -static bool -player_dc_at_next_song(const struct player *player) -{ - return player->dc->pipe != NULL && !player_dc_at_current_song(player); -} - -/** - * Stop the decoder and clears (and frees) its music pipe. - * - * Player lock is not held. - */ -static void -player_dc_stop(struct player *player) -{ - struct decoder_control *dc = player->dc; - - dc_stop(dc); - - if (dc->pipe != NULL) { - /* clear and free the decoder pipe */ - - music_pipe_clear(dc->pipe, player_buffer); - - if (dc->pipe != player->pipe) - music_pipe_free(dc->pipe); - - dc->pipe = NULL; - } -} - -/** - * 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) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); - assert(pc->next_song != NULL); - - player->queued = false; - - if (decoder_lock_has_failed(dc)) { - player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; - pc->next_song = NULL; - player_unlock(pc); - - return false; - } - - player->song = pc->next_song; - player->elapsed_time = 0.0; - - /* set the "starting" flag, which will be cleared by - player_check_decoder_startup() */ - player->decoder_starting = true; - - player_lock(pc); - - /* update player_control's song information */ - pc->total_time = song_get_duration(pc->next_song); - pc->bit_rate = 0; - audio_format_clear(&pc->audio_format); - - /* clear the queued song */ - pc->next_song = NULL; - - player_unlock(pc); - - /* call syncPlaylistWithQueue() in the main thread */ - event_pipe_emit(PIPE_EVENT_PLAYLIST); - - return true; -} - -/** - * 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; -} - -/** - * Wrapper for audio_output_all_open(). Upon failure, it pauses the - * player. - * - * @return true on success - */ -static bool -player_open_output(struct player *player) -{ - struct player_control *pc = player->pc; - - assert(audio_format_defined(&player->play_audio_format)); - assert(pc->state == PLAYER_STATE_PLAY || - pc->state == PLAYER_STATE_PAUSE); - - if (audio_output_all_open(&player->play_audio_format, player_buffer)) { - player->output_open = true; - player->paused = false; - - player_lock(pc); - pc->state = PLAYER_STATE_PLAY; - player_unlock(pc); - - idle_add(IDLE_PLAYER); - - return true; - } else { - player->output_open = false; - - /* pause: the user may resume playback as soon as an - audio output becomes available */ - player->paused = true; - - player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; - pc->state = PLAYER_STATE_PAUSE; - player_unlock(pc); - - idle_add(IDLE_PLAYER); - - return false; - } -} - -/** - * 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 player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->decoder_starting); - - decoder_lock(dc); - - if (decoder_has_failed(dc)) { - /* the decoder failed */ - decoder_unlock(dc); - - player_lock(pc); - pc->errored_song = dc->song; - pc->error = PLAYER_ERROR_FILE; - player_unlock(pc); - - return false; - } else if (!decoder_is_starting(dc)) { - /* the decoder is ready and ok */ - - decoder_unlock(dc); - - if (player->output_open && - !audio_output_all_wait(pc, 1)) - /* the output devices havn't finished playing - all chunks yet - wait for that */ - return true; - - player_lock(pc); - pc->total_time = real_song_duration(dc->song, dc->total_time); - pc->audio_format = dc->in_audio_format; - player_unlock(pc); - - idle_add(IDLE_PLAYER); - - player->play_audio_format = dc->out_audio_format; - player->decoder_starting = false; - - if (!player->paused && !player_open_output(player)) { - char *uri = song_get_uri(dc->song); - g_warning("problems opening audio device " - "while playing \"%s\"", uri); - g_free(uri); - - return true; - } - - return true; - } else { - /* the decoder is not yet ready; wait - some more */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - - return true; - } -} - -/** - * 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) -{ - assert(player->output_open); - assert(audio_format_defined(&player->play_audio_format)); - - struct music_chunk *chunk = music_buffer_allocate(player_buffer); - if (chunk == NULL) { - g_warning("Failed to allocate silence buffer"); - return false; - } - -#ifndef NDEBUG - 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); - - if (!audio_output_all_play(chunk)) { - music_buffer_return(player_buffer, chunk); - return false; - } - - return true; -} - -/** - * This is the handler for the #PLAYER_COMMAND_SEEK command. - * - * The player lock is not held. - */ -static bool player_seek_decoder(struct player *player) -{ - struct player_control *pc = player->pc; - struct song *song = pc->next_song; - struct decoder_control *dc = player->dc; - - assert(pc->next_song != NULL); - - const unsigned start_ms = song->start_ms; - - if (decoder_current_song(dc) != song) { - /* the decoder is already decoding the "next" song - - stop it and start the previous song again */ - - player_dc_stop(player); - - /* clear music chunks which might still reside in the - pipe */ - music_pipe_clear(player->pipe, player_buffer); - - /* re-start the decoder */ - player_dc_start(player, player->pipe); - if (!player_wait_for_decoder(player)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - } else { - if (!player_dc_at_current_song(player)) { - /* the decoder is already decoding the "next" song, - but it is the same song file; exchange the pipe */ - music_pipe_clear(player->pipe, player_buffer); - music_pipe_free(player->pipe); - player->pipe = dc->pipe; - } - - pc->next_song = NULL; - player->queued = false; - } - - /* wait for the decoder to complete initialization */ - - while (player->decoder_starting) { - if (!player_check_decoder_startup(player)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - } - - /* send the SEEK command */ - - double where = pc->seek_where; - if (where > pc->total_time) - where = pc->total_time - 0.1; - if (where < 0.0) - where = 0.0; - - if (!dc_seek(dc, where + start_ms / 1000.0)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - - player->elapsed_time = where; - - player_command_finished(pc); - - player->xfade = XFADE_UNKNOWN; - - /* re-fill the buffer after seeking */ - player->buffering = true; - - audio_output_all_cancel(); - - return true; -} - -/** - * Player lock must be held before calling. - */ -static void player_process_command(struct player *player) -{ - struct player_control *pc = player->pc; - G_GNUC_UNUSED struct decoder_control *dc = player->dc; - - switch (pc->command) { - case PLAYER_COMMAND_NONE: - case PLAYER_COMMAND_STOP: - case PLAYER_COMMAND_EXIT: - case PLAYER_COMMAND_CLOSE_AUDIO: - break; - - case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); - audio_output_all_enable_disable(); - player_lock(pc); - player_command_finished_locked(pc); - 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_locked(pc); - break; - - case PLAYER_COMMAND_PAUSE: - player_unlock(pc); - - player->paused = !player->paused; - if (player->paused) { - audio_output_all_pause(); - player_lock(pc); - - 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); - - pc->state = PLAYER_STATE_PLAY; - } else { - player_open_output(player); - - player_lock(pc); - } - - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_SEEK: - player_unlock(pc); - player_seek_decoder(player); - player_lock(pc); - break; - - case PLAYER_COMMAND_CANCEL: - if (pc->next_song == NULL) { - /* the cancel request arrived too late, we're - already playing the queued song... stop - everything now */ - pc->command = PLAYER_COMMAND_STOP; - return; - } - - if (player_dc_at_next_song(player)) { - /* the decoder is already decoding the song - - stop it and reset the position */ - player_unlock(pc); - player_dc_stop(player); - player_lock(pc); - } - - pc->next_song = NULL; - player->queued = false; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_REFRESH: - if (player->output_open && !player->paused) { - player_unlock(pc); - audio_output_all_check(); - player_lock(pc); - } - - 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(pc); - 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 player_control *pc, - struct song *song, struct music_chunk *chunk, - const struct audio_format *format) -{ - assert(music_chunk_check_format(chunk, format)); - - if (chunk->tag != NULL) - update_song_tag(song, chunk->tag); - - if (chunk->length == 0) { - music_buffer_return(player_buffer, chunk); - return true; - } - - player_lock(pc); - pc->bit_rate = chunk->bit_rate; - player_unlock(pc); - - /* send the chunk to the audio outputs */ - - if (!audio_output_all_play(chunk)) - return false; - - pc->total_play_time += (double)chunk->length / - audio_format_time_to_size(format); - return true; -} - -/** - * Obtains the next chunk from the music pipe, optionally applies - * cross-fading, and sends it to all audio outputs. - * - * @return true on success, false on error (playback will be stopped) - */ -static bool -play_next_chunk(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - if (!audio_output_all_wait(pc, 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); - - if (!player->cross_fading) { - /* beginning of the cross fade - adjust - crossFadeChunks which might be bigger than - the remaining number of chunks in the old - song */ - player->cross_fade_chunks = cross_fade_position; - player->cross_fading = true; - } - - 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 - the current song is faded out */ - player->cross_fade_tag = - tag_merge_replace(player->cross_fade_tag, - other_chunk->tag); - other_chunk->tag = NULL; - - 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 */ - - 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 */ - decoder_signal(dc); - player_wait_decoder(pc, dc); - decoder_unlock(dc); - - return true; - } - } - } - - if (chunk == NULL) - chunk = music_pipe_shift(player->pipe); - - assert(chunk != NULL); - - /* insert the postponed tag if cross-fading is finished */ - - if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) { - chunk->tag = tag_merge_replace(chunk->tag, - player->cross_fade_tag); - player->cross_fade_tag = NULL; - } - - /* play the current chunk */ - - if (!play_chunk(player->pc, player->song, chunk, - &player->play_audio_format)) { - music_buffer_return(player_buffer, chunk); - - player_lock(pc); - - 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(pc); - - idle_add(IDLE_PLAYER); - - 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 */ - decoder_lock(dc); - if (!decoder_is_idle(dc) && - music_pipe_size(dc->pipe) <= (pc->buffered_before_play + - music_buffer_size(player_buffer) * 3) / 4) - decoder_signal(dc); - decoder_unlock(dc); - - return true; -} - -/** - * This is called at the border between two songs: the audio output - * 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 -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 = player->dc->pipe; - - audio_output_all_song_border(); - - if (!player_wait_for_decoder(player)) - return false; - - struct player_control *const pc = player->pc; - player_lock(pc); - - const bool border_pause = pc->border_pause; - if (border_pause) { - player->paused = true; - pc->state = PLAYER_STATE_PAUSE; - } - - player_unlock(pc); - - if (border_pause) - idle_add(IDLE_PLAYER); - - return true; -} - -/* - * The main loop of the player thread, during playback. This is - * basically a state machine, which multiplexes data between the - * decoder thread and the output threads. - */ -static void do_play(struct player_control *pc, struct decoder_control *dc) -{ - struct player player = { - .pc = pc, - .dc = dc, - .buffering = true, - .decoder_starting = false, - .paused = false, - .queued = true, - .output_open = false, - .song = NULL, - .xfade = XFADE_UNKNOWN, - .cross_fading = false, - .cross_fade_chunks = 0, - .cross_fade_tag = NULL, - .elapsed_time = 0.0, - }; - - player_unlock(pc); - - player.pipe = music_pipe_new(); - - player_dc_start(&player, player.pipe); - if (!player_wait_for_decoder(&player)) { - player_dc_stop(&player); - player_command_finished(pc); - music_pipe_free(player.pipe); - event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(pc); - return; - } - - player_lock(pc); - pc->state = PLAYER_STATE_PLAY; - - if (pc->command == PLAYER_COMMAND_SEEK) - player.elapsed_time = pc->seek_where; - - player_command_finished_locked(pc); - - while (true) { - player_process_command(&player); - if (pc->command == PLAYER_COMMAND_STOP || - pc->command == PLAYER_COMMAND_EXIT || - pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { - player_unlock(pc); - audio_output_all_cancel(); - break; - } - - player_unlock(pc); - - if (player.buffering) { - /* buffering at the start of the song - wait - until the buffer is large enough, to - prevent stuttering on slow machines */ - - if (music_pipe_size(player.pipe) < pc->buffered_before_play && - !decoder_lock_is_idle(dc)) { - /* not enough decoded buffer space yet */ - - if (!player.paused && - player.output_open && - audio_output_all_check() < 4 && - !player_send_silence(&player)) - break; - - decoder_lock(dc); - /* XXX race condition: check decoder again */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - player_lock(pc); - continue; - } else { - /* buffering is complete */ - player.buffering = false; - } - } - - if (player.decoder_starting) { - /* wait until the decoder is initialized completely */ - - if (!player_check_decoder_startup(&player)) - break; - - player_lock(pc); - continue; - } - -#ifndef NDEBUG - /* - music_pipe_check_format(&play_audio_format, - player.next_song_chunk, - &dc->out_audio_format); - */ -#endif - - 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(dc->pipe == NULL || dc->pipe == player.pipe); - - player_dc_start(&player, music_pipe_new()); - } - - if (/* no cross-fading if MPD is going to pause at the - end of the current song */ - !pc->border_pause && - player_dc_at_next_song(&player) && - player.xfade == XFADE_UNKNOWN && - !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, - 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); - if (player.cross_fade_chunks > 0) { - player.xfade = XFADE_ENABLED; - player.cross_fading = false; - } else - /* cross fading is disabled or the - next song is too short */ - player.xfade = XFADE_DISABLED; - } - - if (player.paused) { - player_lock(pc); - - if (pc->command == PLAYER_COMMAND_NONE) - player_wait(pc); - continue; - } else if (!music_pipe_empty(player.pipe)) { - /* at least one music chunk is ready - send it - to the audio output */ - - play_next_chunk(&player); - } else if (audio_output_all_check() > 0) { - /* not enough data from decoder, but the - output thread is still busy, so it's - okay */ - - /* XXX synchronize in a better way */ - g_usleep(10000); - } else if (player_dc_at_next_song(&player)) { - /* at the beginning of a new song */ - - if (!player_song_border(&player)) - break; - } 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)) { - /* wait for the hardware to finish - playback */ - audio_output_all_drain(); - break; - } - } else if (player.output_open) { - /* the decoder is too busy and hasn't provided - new PCM data in time: send silence (if the - output pipe is empty) */ - if (!player_send_silence(&player)) - break; - } - - player_lock(pc); - } - - player_dc_stop(&player); - - music_pipe_clear(player.pipe, player_buffer); - music_pipe_free(player.pipe); - - if (player.cross_fade_tag != NULL) - tag_free(player.cross_fade_tag); - - player_lock(pc); - - if (player.queued) { - assert(pc->next_song != NULL); - pc->next_song = NULL; - } - - pc->state = PLAYER_STATE_STOP; - - player_unlock(pc); - - event_pipe_emit(PIPE_EVENT_PLAYLIST); - - player_lock(pc); -} - -static gpointer -player_task(gpointer arg) -{ - struct player_control *pc = arg; - - struct decoder_control *dc = dc_new(pc->cond); - decoder_thread_start(dc); - - player_buffer = music_buffer_new(pc->buffer_chunks); - - player_lock(pc); - - while (1) { - switch (pc->command) { - case PLAYER_COMMAND_SEEK: - case PLAYER_COMMAND_QUEUE: - assert(pc->next_song != NULL); - - do_play(pc, dc); - break; - - case PLAYER_COMMAND_STOP: - player_unlock(pc); - audio_output_all_cancel(); - player_lock(pc); - - /* fall through */ - - case PLAYER_COMMAND_PAUSE: - pc->next_song = NULL; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_CLOSE_AUDIO: - player_unlock(pc); - - audio_output_all_release(); - - player_lock(pc); - player_command_finished_locked(pc); - -#ifndef NDEBUG - /* in the DEBUG build, check for leaked - music_chunk objects by freeing the - music_buffer */ - music_buffer_free(player_buffer); - player_buffer = music_buffer_new(pc->buffer_chunks); -#endif - - break; - - case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); - audio_output_all_enable_disable(); - player_lock(pc); - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_EXIT: - player_unlock(pc); - - dc_quit(dc); - dc_free(dc); - audio_output_all_close(); - music_buffer_free(player_buffer); - - player_command_finished(pc); - return NULL; - - case PLAYER_COMMAND_CANCEL: - pc->next_song = NULL; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_REFRESH: - /* no-op when not playing */ - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_NONE: - player_wait(pc); - break; - } - } -} - -void -player_create(struct player_control *pc) -{ - assert(pc->thread == NULL); - - GError *e = NULL; - pc->thread = g_thread_create(player_task, pc, true, &e); - if (pc->thread == NULL) - MPD_ERROR("Failed to spawn player task: %s", e->message); -} diff --git a/src/player_thread.h b/src/player_thread.h deleted file mode 100644 index 7373eb438..000000000 --- a/src/player_thread.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * The player thread controls the playback. It acts as a bridge - * between the decoder thread and the output thread(s): it receives - * #music_chunk objects from the decoder, optionally mixes them - * (cross-fading), applies software volume, and sends them to the - * audio outputs via audio_output_all_play(). - * - * It is controlled by the main thread (the playlist code), see - * player_control.h. The playlist enqueues new songs into the player - * thread and sends it commands. - * - * The player thread itself does not do any I/O. It synchronizes with - * other threads via #GMutex and #GCond objects, and passes - * #music_chunk instances around in #music_pipe objects. - */ - -#ifndef MPD_PLAYER_THREAD_H -#define MPD_PLAYER_THREAD_H - -struct player_control; - -void -player_create(struct player_control *pc); - -#endif diff --git a/src/playlist.c b/src/playlist.c deleted file mode 100644 index dc6d8c340..000000000 --- a/src/playlist.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_internal.h" -#include "playlist_save.h" -#include "player_control.h" -#include "command.h" -#include "tag.h" -#include "song.h" -#include "conf.h" -#include "stored_playlist.h" -#include "idle.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_increment_version_all(struct playlist *playlist) -{ - queue_modify_all(&playlist->queue); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_tag_changed(struct playlist *playlist) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - queue_modify(&playlist->queue, playlist->current); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_init(struct playlist *playlist) -{ - queue_init(&playlist->queue, - config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH)); - - playlist->queued = -1; - playlist->current = -1; -} - -void -playlist_finish(struct playlist *playlist) -{ - queue_finish(&playlist->queue); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, - unsigned order) -{ - struct song *song; - char *uri; - - assert(queue_valid_order(&playlist->queue, order)); - - playlist->queued = order; - - song = queue_get_order(&playlist->queue, order); - uri = song_get_uri(song); - g_debug("queue song %i:\"%s\"", playlist->queued, uri); - g_free(uri); - - pc_enqueue_song(pc, song); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(struct playlist *playlist, struct player_control *pc) -{ - assert(pc->next_song == NULL); - assert(playlist->queued >= -1); - - /* queued song has started: copy queued to current, - and notify the clients */ - - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; - - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); - - idle_add(IDLE_PLAYER); -} - -const struct song * -playlist_get_queued_song(struct playlist *playlist) -{ - if (!playlist->playing || playlist->queued < 0) - return NULL; - - return queue_get_order(&playlist->queue, playlist->queued); -} - -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev) -{ - int next_order; - const struct song *next_song; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert((playlist->queued < 0) == (prev == NULL)); - - next_order = playlist->current >= 0 - ? queue_next_order(&playlist->queue, playlist->current) - : 0; - - if (next_order == 0 && playlist->queue.random && - !playlist->queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that the playlist->current still points to - the current song, after the song order has been - shuffled */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } - - if (next_order >= 0) - next_song = queue_get_order(&playlist->queue, next_order); - else - next_song = NULL; - - if (prev != NULL && next_song != prev) { - /* clear the currently queued song */ - pc_cancel(pc); - playlist->queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(playlist, pc, next_order); - else - playlist->queued = next_order; - } -} - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum) -{ - struct song *song; - char *uri; - - playlist->playing = true; - playlist->queued = -1; - - song = queue_get_order(&playlist->queue, orderNum); - - uri = song_get_uri(song); - g_debug("play %i:\"%s\"", orderNum, uri); - g_free(uri); - - pc_play(pc, song); - playlist->current = orderNum; -} - -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc); - -/** - * This is the "PLAYLIST" event handler. It is invoked by the player - * thread whenever it requests a new queued song, or when it exits. - */ -void -playlist_sync(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - player_lock(pc); - enum player_state pc_state = pc_get_state(pc); - const struct song *pc_next_song = pc->next_song; - player_unlock(pc); - - if (pc_state == PLAYER_STATE_STOP) - /* the player thread has stopped: check if playback - should be restarted with the next song. That can - happen if the playlist isn't filling the queue fast - enough */ - playlist_resume_playback(playlist, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist, pc); - - player_lock(pc); - pc_next_song = pc->next_song; - player_unlock(pc); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, pc, NULL); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc) -{ - enum player_error error; - - assert(playlist->playing); - assert(pc_get_state(pc) == PLAYER_STATE_STOP); - - error = pc_get_error(pc); - if (error == PLAYER_ERROR_NOERROR) - playlist->error_count = 0; - else - ++playlist->error_count; - - if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) || - error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM || - playlist->error_count >= queue_length(&playlist->queue)) - /* too many errors, or critical error: stop - playback */ - playlist_stop(playlist, pc); - else - /* continue playback at the next song */ - playlist_next(playlist, pc); -} - -bool -playlist_get_repeat(const struct playlist *playlist) -{ - return playlist->queue.repeat; -} - -bool -playlist_get_random(const struct playlist *playlist) -{ - return playlist->queue.random; -} - -bool -playlist_get_single(const struct playlist *playlist) -{ - return playlist->queue.single; -} - -bool -playlist_get_consume(const struct playlist *playlist) -{ - return playlist->queue.consume; -} - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.repeat) - return; - - struct queue *queue = &playlist->queue; - - queue->repeat = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(struct playlist *playlist) -{ - if (playlist->current >= 0) - /* update playlist.current, order==position now */ - playlist->current = queue_order_to_position(&playlist->queue, - playlist->current); - - queue_restore_order(&playlist->queue); -} - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.single) - return; - - struct queue *queue = &playlist->queue; - - queue->single = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_consume(struct playlist *playlist, bool status) -{ - if (status == playlist->queue.consume) - return; - - playlist->queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status) -{ - const struct song *queued; - - if (status == playlist->queue.random) - return; - - queued = playlist_get_queued_song(playlist); - - playlist->queue.random = status; - - if (playlist->queue.random) { - /* shuffle the queue order, but preserve - playlist->current */ - - int current_position = - playlist->playing && playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - queue_shuffle_order(&playlist->queue); - - if (current_position >= 0) { - /* make sure the current song is the first in - the order list, so the whole rest of the - playlist is played after that */ - unsigned current_order = - queue_position_to_order(&playlist->queue, - current_position); - queue_swap_order(&playlist->queue, 0, current_order); - playlist->current = 0; - } else - playlist->current = -1; - } else - playlist_order(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - idle_add(IDLE_OPTIONS); -} - -int -playlist_get_current_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - return queue_order_to_position(&playlist->queue, - playlist->current); - - return -1; -} - -int -playlist_get_next_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - { - 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) - return queue_order_to_position(&playlist->queue, 0); - } - - return -1; -} - -unsigned long -playlist_get_version(const struct playlist *playlist) -{ - return playlist->queue.version; -} - -int -playlist_get_length(const struct playlist *playlist) -{ - return queue_length(&playlist->queue); -} - -unsigned -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 deleted file mode 100644 index a21bdf24a..000000000 --- a/src/playlist.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_H -#define MPD_PLAYLIST_H - -#include "queue.h" -#include "playlist_error.h" - -#include <stdbool.h> - -struct player_control; - -struct playlist { - /** - * The song queue - it contains the "real" playlist. - */ - struct queue queue; - - /** - * This value is true if the player is currently playing (or - * should be playing). - */ - bool playing; - - /** - * If true, then any error is fatal; if false, MPD will - * attempt to play the next song on non-fatal errors. During - * seeking, this flag is set. - */ - bool stop_on_error; - - /** - * Number of errors since playback was started. If this - * number exceeds the length of the playlist, MPD gives up, - * because all songs have been tried. - */ - unsigned error_count; - - /** - * The "current song pointer". This is the song which is - * played when we get the "play" command. It is also the song - * which is currently being played. - */ - int current; - - /** - * The "next" song to be played, when the current one - * finishes. The decoder thread may start decoding and - * buffering it, while the "current" song is still playing. - * - * This variable is only valid if #playing is true. - */ - int queued; -}; - -/** the global playlist object */ -extern struct playlist g_playlist; - -void -playlist_global_init(void); - -void -playlist_global_finish(void); - -void -playlist_init(struct playlist *playlist); - -void -playlist_finish(struct playlist *playlist); - -void -playlist_tag_changed(struct playlist *playlist); - -/** - * Returns the "queue" object of the global playlist instance. - */ -static inline const struct queue * -playlist_get_queue(const struct playlist *playlist) -{ - return &playlist->queue; -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc); - -/** - * Appends a local file (outside the music database) to the playlist. - * - * Note: the caller is responsible for checking permissions. - */ -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id); - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *file, unsigned *added_id); - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id); - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - 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, struct player_control *pc, - unsigned start, unsigned end); - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned song); - -void -playlist_stop(struct playlist *playlist, struct player_control *pc); - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song); - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int song); - -void -playlist_next(struct playlist *playlist, struct player_control *pc); - -void -playlist_sync(struct playlist *playlist, struct player_control *pc); - -void -playlist_previous(struct playlist *playlist, struct player_control *pc); - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song); - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to); - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id, int to); - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2); - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2); - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start_position, unsigned end_position, - uint8_t priority); - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority); - -bool -playlist_get_repeat(const struct playlist *playlist); - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_random(const struct playlist *playlist); - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_single(const struct playlist *playlist); - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_consume(const struct playlist *playlist); - -void -playlist_set_consume(struct playlist *playlist, bool status); - -int -playlist_get_current_song(const struct playlist *playlist); - -int -playlist_get_next_song(const struct playlist *playlist); - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song); - -int -playlist_get_length(const struct playlist *playlist); - -unsigned long -playlist_get_version(const struct playlist *playlist); - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time); - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time); - -/** - * Seek within the current song. Fails if MPD is not currently - * playing. - * - * @param time the time in seconds - * @param relative if true, then the specified time is relative to the - * current position - */ -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative); - -void -playlist_increment_version_all(struct playlist *playlist); - -#endif diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx new file mode 100644 index 000000000..4dcbf56b9 --- /dev/null +++ b/src/playlist/AsxPlaylistPlugin.cxx @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "AsxPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "MemorySongEnumerator.hxx" +#include "InputStream.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain asx_domain("asx"); + +/** + * This is the state object for the GLib XML parser. + */ +struct AsxParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<SongPointer> 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. + */ + Song *song; + + AsxParser() + :state(ROOT) {} + +}; + +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(gcc_unused GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, gcc_unused GError **error) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + parser->state = AsxParser::ENTRY; + parser->song = Song::NewRemote("asx:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case AsxParser::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 */ + Song *song = Song::NewRemote(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + parser->song->Free(); + } + + 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(gcc_unused GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, gcc_unused GError **error) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + if (strcmp(parser->song->uri, "asx:") != 0) + parser->songs.emplace_front(parser->song); + else + parser->song->Free(); + + parser->state = AsxParser::ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +asx_text(gcc_unused GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, gcc_unused GError **error) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = new Tag(); + parser->song->tag->AddItem(parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser asx_parser = { + asx_start_element, + asx_end_element, + asx_text, + nullptr, + nullptr, +}; + +static void +asx_parser_destroy(gpointer data) +{ + AsxParser *parser = (AsxParser *)data; + + if (parser->state >= AsxParser::ENTRY) + parser->song->Free(); +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +asx_open_stream(struct input_stream *is) +{ + AsxParser parser; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + Error error2; + 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 = is->LockRead(buffer, sizeof(buffer), error2); + if (nbytes == 0) { + if (error2.IsDefined()) { + g_markup_parse_context_free(context); + LogError(error2); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + FormatErrno(asx_domain, + "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) { + FormatErrno(asx_domain, + "XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + parser.songs.reverse(); + MemorySongEnumerator *playlist = + new MemorySongEnumerator(std::move(parser.songs)); + + g_markup_parse_context_free(context); + + return playlist; +} + +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 = { + "asx", + + nullptr, + nullptr, + nullptr, + asx_open_stream, + + nullptr, + asx_suffixes, + asx_mime_types, +}; diff --git a/src/playlist/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx new file mode 100644 index 000000000..240c1824a --- /dev/null +++ b/src/playlist/AsxPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ASX_PLAYLIST_PLUGIN_HXX +#define MPD_ASX_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin asx_playlist_plugin; + +#endif diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx new file mode 100644 index 000000000..5b0b6c6d2 --- /dev/null +++ b/src/playlist/CuePlaylistPlugin.cxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "CuePlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "SongEnumerator.hxx" +#include "tag/Tag.hxx" +#include "Song.hxx" +#include "cue/CueParser.hxx" +#include "TextInputStream.hxx" + +#include <assert.h> +#include <string.h> + +class CuePlaylist final : public SongEnumerator { + struct input_stream *is; + TextInputStream tis; + CueParser parser; + + public: + CuePlaylist(struct input_stream *_is) + :is(_is), tis(is) { + } + + virtual Song *NextSong() override; +}; + +static SongEnumerator * +cue_playlist_open_stream(struct input_stream *is) +{ + return new CuePlaylist(is); +} + +Song * +CuePlaylist::NextSong() +{ + Song *song = parser.Get(); + if (song != NULL) + return song; + + std::string line; + while (tis.ReadLine(line)) { + parser.Feed(line.c_str()); + song = parser.Get(); + if (song != NULL) + return song; + } + + parser.Finish(); + return parser.Get(); +} + +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 = { + "cue", + + nullptr, + nullptr, + nullptr, + cue_playlist_open_stream, + + nullptr, + cue_playlist_suffixes, + cue_playlist_mime_types, +}; diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx new file mode 100644 index 000000000..cf5e3a8f0 --- /dev/null +++ b/src/playlist/CuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX +#define MPD_CUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin cue_playlist_plugin; + +#endif diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx new file mode 100644 index 000000000..a1a865c08 --- /dev/null +++ b/src/playlist/DespotifyPlaylistPlugin.cxx @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DespotifyPlaylistPlugin.hxx" +#include "DespotifyUtils.hxx" +#include "PlaylistPlugin.hxx" +#include "MemorySongEnumerator.hxx" +#include "tag/Tag.hxx" +#include "Song.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <string.h> +#include <stdlib.h> + +static void +add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) +{ + const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; + Song *song; + char uri[128]; + char *ds_uri; + + /* Create a spt://... URI for MPD */ + snprintf(uri, sizeof(uri), "%s://", dsp_scheme); + ds_uri = uri + strlen(dsp_scheme) + 3; + + if (despotify_track_to_uri(track, ds_uri) != ds_uri) { + /* Should never really fail, but let's be sure */ + FormatDebug(despotify_domain, + "Can't add track %s", track->title); + return; + } + + song = Song::NewRemote(uri); + song->tag = mpd_despotify_tag_from_track(track); + + songs.emplace_front(song); +} + +static bool +parse_track(struct despotify_session *session, + std::forward_list<SongPointer> &songs, + struct ds_link *link) +{ + struct ds_track *track = despotify_link_get_track(session, link); + if (track == nullptr) + return false; + + add_song(songs, track); + return true; +} + +static bool +parse_playlist(struct despotify_session *session, + std::forward_list<SongPointer> &songs, + struct ds_link *link) +{ + ds_playlist *playlist = despotify_link_get_playlist(session, link); + if (playlist == nullptr) + return false; + + for (ds_track *track = playlist->tracks; track != nullptr; + track = track->next) + add_song(songs, track); + + return true; +} + +static SongEnumerator * +despotify_playlist_open_uri(const char *url, + gcc_unused Mutex &mutex, gcc_unused Cond &cond) +{ + despotify_session *session = mpd_despotify_get_session(); + if (session == nullptr) + return nullptr; + + /* Get link without spt:// */ + ds_link *link = + despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); + if (link == nullptr) { + FormatDebug(despotify_domain, "Can't find %s\n", url); + return nullptr; + } + + std::forward_list<SongPointer> songs; + + bool parse_result; + switch (link->type) { + case LINK_TYPE_TRACK: + parse_result = parse_track(session, songs, link); + break; + case LINK_TYPE_PLAYLIST: + parse_result = parse_playlist(session, songs, link); + break; + default: + parse_result = false; + break; + } + + despotify_free_link(link); + if (!parse_result) + return nullptr; + + songs.reverse(); + return new MemorySongEnumerator(std::move(songs)); +} + +static const char *const despotify_schemes[] = { + "spt", + nullptr +}; + +const struct playlist_plugin despotify_playlist_plugin = { + "despotify", + + nullptr, + nullptr, + despotify_playlist_open_uri, + nullptr, + + despotify_schemes, + nullptr, + nullptr, +}; diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/DespotifyPlaylistPlugin.hxx new file mode 100644 index 000000000..c1e5b7f39 --- /dev/null +++ b/src/playlist/DespotifyPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin despotify_playlist_plugin; + +#endif diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx new file mode 100644 index 000000000..5fc1f28e0 --- /dev/null +++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Playlist plugin that reads embedded cue sheets from the "CUESHEET" + * tag of a music file. + */ + +#include "config.h" +#include "EmbeddedCuePlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "SongEnumerator.hxx" +#include "tag/Tag.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagId3.hxx" +#include "tag/ApeTag.hxx" +#include "Song.hxx" +#include "TagFile.hxx" +#include "cue/CueParser.hxx" + +#include <glib.h> +#include <assert.h> +#include <string.h> + +class EmbeddedCuePlaylist final : public SongEnumerator { +public: + /** + * This is an override for the CUE's "FILE". An embedded CUE + * sheet must always point to the song file it is contained + * in. + */ + char *filename; + + /** + * The value of the file's "CUESHEET" tag. + */ + char *cuesheet; + + /** + * The offset of the next line within "cuesheet". + */ + char *next; + + CueParser *parser; + +public: + EmbeddedCuePlaylist() + :filename(nullptr), cuesheet(nullptr), parser(nullptr) { + } + + virtual ~EmbeddedCuePlaylist() { + delete parser; + g_free(cuesheet); + g_free(filename); + } + + virtual Song *NextSong() override; +}; + +static void +embcue_tag_pair(const char *name, const char *value, void *ctx) +{ + EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx; + + if (playlist->cuesheet == NULL && + g_ascii_strcasecmp(name, "cuesheet") == 0) + playlist->cuesheet = g_strdup(value); +} + +static const struct tag_handler embcue_tag_handler = { + nullptr, + nullptr, + embcue_tag_pair, +}; + +static SongEnumerator * +embcue_playlist_open_uri(const char *uri, + gcc_unused Mutex &mutex, + gcc_unused Cond &cond) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + const auto playlist = new EmbeddedCuePlaylist(); + + tag_file_scan(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) { + tag_ape_scan2(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) + tag_id3_scan(uri, &embcue_tag_handler, playlist); + } + + if (playlist->cuesheet == NULL) { + /* no "CUESHEET" tag found */ + delete playlist; + return NULL; + } + + playlist->filename = g_path_get_basename(uri); + + playlist->next = playlist->cuesheet; + playlist->parser = new CueParser(); + + return playlist; +} + +Song * +EmbeddedCuePlaylist::NextSong() +{ + Song *song = parser->Get(); + if (song != NULL) + return song; + + while (*next != 0) { + const char *line = next; + char *eol = strpbrk(next, "\r\n"); + if (eol != NULL) { + /* null-terminate the line */ + *eol = 0; + next = eol + 1; + } else + /* last line; put the "next" pointer to the + end of the buffer */ + next += strlen(line); + + parser->Feed(line); + song = parser->Get(); + if (song != NULL) + return song->ReplaceURI(filename); + } + + parser->Finish(); + song = parser->Get(); + if (song != NULL) + song = song->ReplaceURI(filename); + return song; +} + +static const char *const embcue_playlist_suffixes[] = { + /* a few codecs that are known to be supported; there are + probably many more */ + "flac", + "mp3", "mp2", + "mp4", "mp4a", "m4b", + "ape", + "wv", + "ogg", "oga", + NULL +}; + +const struct playlist_plugin embcue_playlist_plugin = { + "cue", + + nullptr, + nullptr, + embcue_playlist_open_uri, + nullptr, + + embcue_playlist_suffixes, + nullptr, + nullptr, +}; diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx new file mode 100644 index 000000000..e306730f4 --- /dev/null +++ b/src/playlist/EmbeddedCuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EMBCUE_PLAYLIST_PLUGIN_HXX +#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin embcue_playlist_plugin; + +#endif diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx new file mode 100644 index 000000000..177e8857d --- /dev/null +++ b/src/playlist/ExtM3uPlaylistPlugin.cxx @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ExtM3uPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "SongEnumerator.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" +#include "util/StringUtil.hxx" +#include "TextInputStream.hxx" + +#include <glib.h> + +#include <string.h> +#include <stdlib.h> + +class ExtM3uPlaylist final : public SongEnumerator { + TextInputStream tis; + +public: + ExtM3uPlaylist(input_stream *is) + :tis(is) { + } + + bool CheckFirstLine() { + std::string line; + return tis.ReadLine(line) && + strcmp(line.c_str(), "#EXTM3U") == 0; + } + + virtual Song *NextSong() override; +}; + +static SongEnumerator * +extm3u_open_stream(struct input_stream *is) +{ + ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is); + + if (!playlist->CheckFirstLine()) { + /* no EXTM3U header: fall back to the plain m3u + plugin */ + delete playlist; + return NULL; + } + + return playlist; +} + +/** + * Parse a EXTINF line. + * + * @param line the rest of the input line after the colon + */ +static Tag * +extm3u_parse_tag(const char *line) +{ + long duration; + char *endptr; + const char *name; + Tag *tag; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return NULL; + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = strchug_fast_c(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return NULL; + + tag = new Tag(); + 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->AddItem(TAG_NAME, name); + + return tag; +} + +Song * +ExtM3uPlaylist::NextSong() +{ + Tag *tag = NULL; + std::string line; + const char *line_s; + Song *song; + + do { + if (!tis.ReadLine(line)) { + delete tag; + return NULL; + } + + line_s = line.c_str(); + + if (g_str_has_prefix(line_s, "#EXTINF:")) { + delete tag; + tag = extm3u_parse_tag(line_s + 8); + continue; + } + + while (*line_s != 0 && g_ascii_isspace(*line_s)) + ++line_s; + } while (line_s[0] == '#' || *line_s == 0); + + song = Song::NewRemote(line_s); + 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 = { + "extm3u", + + nullptr, + nullptr, + nullptr, + extm3u_open_stream, + + nullptr, + extm3u_suffixes, + extm3u_mime_types, +}; diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx new file mode 100644 index 000000000..844fba15c --- /dev/null +++ b/src/playlist/ExtM3uPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EXTM3U_PLAYLIST_PLUGIN_HXX +#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin extm3u_playlist_plugin; + +#endif diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx new file mode 100644 index 000000000..8854be8d7 --- /dev/null +++ b/src/playlist/M3uPlaylistPlugin.cxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "M3uPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "SongEnumerator.hxx" +#include "Song.hxx" +#include "TextInputStream.hxx" + +#include <glib.h> + +class M3uPlaylist final : public SongEnumerator { + TextInputStream tis; + +public: + M3uPlaylist(input_stream *is) + :tis(is) { + } + + virtual Song *NextSong() override; +}; + +static SongEnumerator * +m3u_open_stream(struct input_stream *is) +{ + return new M3uPlaylist(is); +} + +Song * +M3uPlaylist::NextSong() +{ + std::string line; + const char *line_s; + + do { + if (!tis.ReadLine(line)) + return NULL; + + line_s = line.c_str(); + + while (*line_s != 0 && g_ascii_isspace(*line_s)) + ++line_s; + } while (line_s[0] == '#' || *line_s == 0); + + return Song::NewRemote(line_s); +} + +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 = { + "m3u", + + nullptr, + nullptr, + nullptr, + m3u_open_stream, + + nullptr, + m3u_suffixes, + m3u_mime_types, +}; diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/M3uPlaylistPlugin.hxx new file mode 100644 index 000000000..a2058bb29 --- /dev/null +++ b/src/playlist/M3uPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_M3U_PLAYLIST_PLUGIN_HXX +#define MPD_M3U_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin m3u_playlist_plugin; + +#endif diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx new file mode 100644 index 000000000..567add465 --- /dev/null +++ b/src/playlist/PlsPlaylistPlugin.cxx @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlsPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "MemorySongEnumerator.hxx" +#include "InputStream.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string> + +static constexpr Domain pls_domain("pls"); + +static void +pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) +{ + gchar *key; + gchar *value; + int length; + GError *error = NULL; + int num_entries = g_key_file_get_integer(keyfile, "playlist", + "NumberOfEntries", &error); + if (error) { + FormatError(pls_domain, + "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) { + Song *song; + key = g_strdup_printf("File%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + if(error) { + FormatError(pls_domain, "Invalid PLS entry %s: '%s'", + key, error->message); + g_error_free(error); + g_free(key); + return; + } + g_free(key); + + song = Song::NewRemote(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 = new Tag(); + song->tag->AddItem(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 = new Tag(); + song->tag->time = length; + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + + songs.emplace_front(song); + num_entries--; + } + +} + +static SongEnumerator * +pls_open_stream(struct input_stream *is) +{ + GError *error = NULL; + Error error2; + size_t nbytes; + char buffer[1024]; + bool success; + GKeyFile *keyfile; + + std::string kf_data; + + do { + nbytes = is->LockRead(buffer, sizeof(buffer), error2); + if (nbytes == 0) { + if (error2.IsDefined()) { + LogError(error2); + return NULL; + } + + break; + } + + kf_data.append(buffer, nbytes); + /* Limit to 64k */ + } while (kf_data.length() < 65536); + + if (kf_data.empty()) { + LogWarning(pls_domain, "KeyFile parser failed: No Data"); + return NULL; + } + + keyfile = g_key_file_new(); + success = g_key_file_load_from_data(keyfile, + kf_data.data(), kf_data.length(), + G_KEY_FILE_NONE, &error); + + if (!success) { + FormatError(pls_domain, + "KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return NULL; + } + + std::forward_list<SongPointer> songs; + pls_parser(keyfile, songs); + g_key_file_free(keyfile); + + songs.reverse(); + return new MemorySongEnumerator(std::move(songs)); +} + +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 = { + "pls", + + nullptr, + nullptr, + nullptr, + pls_open_stream, + + nullptr, + pls_suffixes, + pls_mime_types, +}; diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/PlsPlaylistPlugin.hxx new file mode 100644 index 000000000..3fafd36d0 --- /dev/null +++ b/src/playlist/PlsPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PLS_PLAYLIST_PLUGIN_HXX +#define MPD_PLS_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin pls_playlist_plugin; + +#endif diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx new file mode 100644 index 000000000..c2becc15a --- /dev/null +++ b/src/playlist/RssPlaylistPlugin.cxx @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "RssPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "MemorySongEnumerator.hxx" +#include "InputStream.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain rss_domain("rss"); + +/** + * This is the state object for the GLib XML parser. + */ +struct RssParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<SongPointer> 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. + */ + Song *song; + + RssParser() + :state(ROOT) {} +}; + +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(gcc_unused GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, gcc_unused GError **error) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + parser->state = RssParser::ITEM; + parser->song = Song::NewRemote("rss:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case RssParser::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 */ + Song *song = Song::NewRemote(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + parser->song->Free(); + } + + 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(gcc_unused GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, gcc_unused GError **error) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + if (strcmp(parser->song->uri, "rss:") != 0) + parser->songs.emplace_front(parser->song); + else + parser->song->Free(); + + parser->state = RssParser::ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +rss_text(gcc_unused GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, gcc_unused GError **error) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = new Tag(); + parser->song->tag->AddItem(parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser rss_parser = { + rss_start_element, + rss_end_element, + rss_text, + nullptr, + nullptr, +}; + +static void +rss_parser_destroy(gpointer data) +{ + RssParser *parser = (RssParser *)data; + + if (parser->state >= RssParser::ITEM) + parser->song->Free(); +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +rss_open_stream(struct input_stream *is) +{ + RssParser parser; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + Error error2; + 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 = is->LockRead(buffer, sizeof(buffer), error2); + if (nbytes == 0) { + if (error2.IsDefined()) { + g_markup_parse_context_free(context); + LogError(error2); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + FormatError(rss_domain, + "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) { + FormatError(rss_domain, + "XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + parser.songs.reverse(); + MemorySongEnumerator *playlist = + new MemorySongEnumerator(std::move(parser.songs)); + + g_markup_parse_context_free(context); + + return playlist; +} + +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 = { + "rss", + + nullptr, + nullptr, + nullptr, + rss_open_stream, + + nullptr, + rss_suffixes, + rss_mime_types, +}; diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx new file mode 100644 index 000000000..f49f7e9cf --- /dev/null +++ b/src/playlist/RssPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_RSS_PLAYLIST_PLUGIN_HXX +#define MPD_RSS_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin rss_playlist_plugin; + +#endif diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx new file mode 100644 index 000000000..99bef29e7 --- /dev/null +++ b/src/playlist/SoundCloudPlaylistPlugin.cxx @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SoundCloudPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "MemorySongEnumerator.hxx" +#include "ConfigData.hxx" +#include "InputStream.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <yajl/yajl_parse.h> + +#include <string.h> + +static struct { + char *apikey; +} soundcloud_config; + +static constexpr Domain soundcloud_domain("soundcloud"); + +static bool +soundcloud_init(const config_param ¶m) +{ + soundcloud_config.apikey = param.DupBlockString("apikey"); + if (soundcloud_config.apikey == NULL) { + LogDebug(soundcloud_domain, + "disabling the soundcloud playlist plugin " + "because API key is not set"); + return false; + } + + return true; +} + +static void +soundcloud_finish(void) +{ + g_free(soundcloud_config.apikey); +} + +/** + * Construct a full soundcloud resolver URL from the given fragment. + * @param uri uri of a soundcloud page (or just the path) + * @return Constructed URL. Must be freed with g_free. + */ +static char * +soundcloud_resolve(const char* uri) { + char *u, *ru; + + if (g_str_has_prefix(uri, "http://")) { + u = g_strdup(uri); + } else if (g_str_has_prefix(uri, "soundcloud.com")) { + u = g_strconcat("http://", uri, NULL); + } else { + /* assume it's just a path on soundcloud.com */ + u = g_strconcat("http://soundcloud.com/", uri, NULL); + } + + ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", soundcloud_config.apikey, NULL); + g_free(u); + + return ru; +} + +/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ + +enum key { + Duration, + Title, + Stream_URL, + Other, +}; + +const char* key_str[] = { + "duration", + "title", + "stream_url", + NULL, +}; + +struct parse_data { + int key; + char* stream_url; + long duration; + char* title; + int got_url; /* nesting level of last stream_url */ + + std::forward_list<SongPointer> songs; +}; + +static int handle_integer(void *ctx, + long +#ifndef HAVE_YAJL1 + long +#endif + intval) +{ + struct parse_data *data = (struct parse_data *) ctx; + + switch (data->key) { + case Duration: + data->duration = intval; + break; + default: + break; + } + + return 1; +} + +static int handle_string(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + const char *s = (const char *) stringval; + + switch (data->key) { + case Title: + if (data->title != NULL) + g_free(data->title); + data->title = g_strndup(s, stringlen); + break; + case Stream_URL: + if (data->stream_url != NULL) + g_free(data->stream_url); + data->stream_url = g_strndup(s, stringlen); + data->got_url = 1; + break; + default: + break; + } + + return 1; +} + +static int handle_mapkey(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + + int i; + data->key = Other; + + for (i = 0; i < Other; ++i) { + if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { + data->key = i; + break; + } + } + + return 1; +} + +static int handle_start_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 0) + data->got_url++; + + return 1; +} + +static int handle_end_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 1) { + data->got_url--; + return 1; + } + + if (data->got_url == 0) + return 1; + + /* got_url == 1, track finished, make it into a song */ + data->got_url = 0; + + Song *s; + char *u; + + u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); + s = Song::NewRemote(u); + g_free(u); + + Tag *t = new Tag(); + t->time = data->duration / 1000; + if (data->title != NULL) + t->AddItem(TAG_NAME, data->title); + s->tag = t; + + data->songs.emplace_front(s); + + return 1; +} + +static yajl_callbacks parse_callbacks = { + NULL, + NULL, + handle_integer, + NULL, + NULL, + handle_string, + handle_start_map, + handle_mapkey, + handle_end_map, + NULL, + NULL, +}; + +/** + * Read JSON data and parse it using the given YAJL parser. + * @param url URL of the JSON data. + * @param hand YAJL parser handle. + * @return -1 on error, 0 on success. + */ +static int +soundcloud_parse_json(const char *url, yajl_handle hand, + Mutex &mutex, Cond &cond) +{ + char buffer[4096]; + unsigned char *ubuffer = (unsigned char *)buffer; + + Error error; + input_stream *input_stream = input_stream::Open(url, mutex, cond, + error); + if (input_stream == NULL) { + if (error.IsDefined()) + LogError(error); + return -1; + } + + mutex.lock(); + input_stream->WaitReady(); + + yajl_status stat; + int done = 0; + + while (!done) { + const size_t nbytes = + input_stream->Read(buffer, sizeof(buffer), error); + if (nbytes == 0) { + if (error.IsDefined()) + LogError(error); + + if (input_stream->IsEOF()) { + done = true; + } else { + mutex.unlock(); + input_stream->Close(); + return -1; + } + } + + if (done) { +#ifdef HAVE_YAJL1 + stat = yajl_parse_complete(hand); +#else + stat = yajl_complete_parse(hand); +#endif + } else + stat = yajl_parse(hand, ubuffer, nbytes); + + if (stat != yajl_status_ok +#ifdef HAVE_YAJL1 + && stat != yajl_status_insufficient_data +#endif + ) + { + unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); + LogError(soundcloud_domain, (const char *)str); + yajl_free_error(hand, str); + break; + } + } + + mutex.unlock(); + input_stream->Close(); + + return 0; +} + +/** + * Parse a soundcloud:// URL and create a playlist. + * @param uri A soundcloud URL. Accepted forms: + * soundcloud://track/<track-id> + * soundcloud://playlist/<playlist-id> + * soundcloud://url/<url or path of soundcloud page> + */ + +static SongEnumerator * +soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + char *s, *p; + char *scheme, *arg, *rest; + s = g_strdup(uri); + scheme = s; + for (p = s; *p; p++) { + if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { + *p = 0; + p += 3; + break; + } + } + arg = p; + for (; *p; p++) { + if (*p == '/') { + *p = 0; + p++; + break; + } + } + rest = p; + + if (strcmp(scheme, "soundcloud") != 0) { + FormatWarning(soundcloud_domain, + "incompatible scheme for soundcloud plugin: %s", + scheme); + g_free(s); + return NULL; + } + + char *u = NULL; + if (strcmp(arg, "track") == 0) { + u = g_strconcat("http://api.soundcloud.com/tracks/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "playlist") == 0) { + u = g_strconcat("http://api.soundcloud.com/playlists/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "url") == 0) { + /* Translate to soundcloud resolver call. libcurl will automatically + follow the redirect to the right resource. */ + u = soundcloud_resolve(rest); + } + g_free(s); + + if (u == NULL) { + LogWarning(soundcloud_domain, "unknown soundcloud URI"); + return NULL; + } + + yajl_handle hand; + struct parse_data data; + + data.got_url = 0; + data.title = NULL; + data.stream_url = NULL; +#ifdef HAVE_YAJL1 + hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); +#else + hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); +#endif + + int ret = soundcloud_parse_json(u, hand, mutex, cond); + + g_free(u); + yajl_free(hand); + if (data.title != NULL) + g_free(data.title); + if (data.stream_url != NULL) + g_free(data.stream_url); + + if (ret == -1) + return NULL; + + data.songs.reverse(); + return new MemorySongEnumerator(std::move(data.songs)); +} + +static const char *const soundcloud_schemes[] = { + "soundcloud", + NULL +}; + +const struct playlist_plugin soundcloud_playlist_plugin = { + "soundcloud", + + soundcloud_init, + soundcloud_finish, + soundcloud_open_uri, + nullptr, + + soundcloud_schemes, + nullptr, + nullptr, +}; + + diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx new file mode 100644 index 000000000..7c121328c --- /dev/null +++ b/src/playlist/SoundCloudPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX +#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin soundcloud_playlist_plugin; + +#endif diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx new file mode 100644 index 000000000..9b55f1962 --- /dev/null +++ b/src/playlist/XspfPlaylistPlugin.cxx @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "XspfPlaylistPlugin.hxx" +#include "PlaylistPlugin.hxx" +#include "MemorySongEnumerator.hxx" +#include "InputStream.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain xspf_domain("xspf"); + +/** + * This is the state object for the GLib XML parser. + */ +struct XspfParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<SongPointer> 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. + */ + Song *song; + + XspfParser() + :state(ROOT) {} +}; + +static void +xspf_start_element(gcc_unused GMarkupParseContext *context, + const gchar *element_name, + gcc_unused const gchar **attribute_names, + gcc_unused const gchar **attribute_values, + gpointer user_data, gcc_unused GError **error) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + if (strcmp(element_name, "playlist") == 0) + parser->state = XspfParser::PLAYLIST; + + break; + + case XspfParser::PLAYLIST: + if (strcmp(element_name, "trackList") == 0) + parser->state = XspfParser::TRACKLIST; + + break; + + case XspfParser::TRACKLIST: + if (strcmp(element_name, "track") == 0) { + parser->state = XspfParser::TRACK; + parser->song = NULL; + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case XspfParser::TRACK: + if (strcmp(element_name, "location") == 0) + parser->state = XspfParser::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 XspfParser::LOCATION: + break; + } +} + +static void +xspf_end_element(gcc_unused GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, gcc_unused GError **error) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + break; + + case XspfParser::PLAYLIST: + if (strcmp(element_name, "playlist") == 0) + parser->state = XspfParser::ROOT; + + break; + + case XspfParser::TRACKLIST: + if (strcmp(element_name, "tracklist") == 0) + parser->state = XspfParser::PLAYLIST; + + break; + + case XspfParser::TRACK: + if (strcmp(element_name, "track") == 0) { + if (parser->song != NULL) + parser->songs.emplace_front(parser->song); + + parser->state = XspfParser::TRACKLIST; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + + case XspfParser::LOCATION: + parser->state = XspfParser::TRACK; + break; + } +} + +static void +xspf_text(gcc_unused GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, gcc_unused GError **error) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + case XspfParser::PLAYLIST: + case XspfParser::TRACKLIST: + break; + + case XspfParser::TRACK: + if (parser->song != NULL && + parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = new Tag(); + parser->song->tag->AddItem(parser->tag, text, text_len); + } + + break; + + case XspfParser::LOCATION: + if (parser->song == NULL) { + char *uri = g_strndup(text, text_len); + parser->song = Song::NewRemote(uri); + g_free(uri); + } + + break; + } +} + +static const GMarkupParser xspf_parser = { + xspf_start_element, + xspf_end_element, + xspf_text, + nullptr, + nullptr, +}; + +static void +xspf_parser_destroy(gpointer data) +{ + XspfParser *parser = (XspfParser *)data; + + if (parser->state >= XspfParser::TRACK && parser->song != NULL) + parser->song->Free(); +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +xspf_open_stream(struct input_stream *is) +{ + XspfParser parser; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + Error error2; + 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 = is->LockRead(buffer, sizeof(buffer), error2); + if (nbytes == 0) { + if (error2.IsDefined()) { + g_markup_parse_context_free(context); + LogError(error2); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + FormatError(xspf_domain, + "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) { + FormatError(xspf_domain, + "XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + parser.songs.reverse(); + MemorySongEnumerator *playlist = + new MemorySongEnumerator(std::move(parser.songs)); + + g_markup_parse_context_free(context); + + return playlist; +} + +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 = { + "xspf", + + nullptr, + nullptr, + nullptr, + xspf_open_stream, + + nullptr, + xspf_suffixes, + xspf_mime_types, +}; diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/XspfPlaylistPlugin.hxx new file mode 100644 index 000000000..fc9bbd2c6 --- /dev/null +++ b/src/playlist/XspfPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_XSPF_PLAYLIST_PLUGIN_HXX +#define MPD_XSPF_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin xspf_playlist_plugin; + +#endif diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c deleted file mode 100644 index 298687859..000000000 --- a/src/playlist/asx_playlist_plugin.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/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_lock_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 deleted file mode 100644 index 6c01c1209..000000000 --- a/src/playlist/asx_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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 deleted file mode 100644 index b85de77d3..000000000 --- a/src/playlist/cue_playlist_plugin.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/cue_playlist_plugin.h" -#include "playlist_plugin.h" -#include "tag.h" -#include "song.h" -#include "cue/cue_parser.h" -#include "input_stream.h" -#include "text_input_stream.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "cue" - -struct cue_playlist { - struct playlist_provider base; - - struct input_stream *is; - struct text_input_stream *tis; - struct cue_parser *parser; -}; - -static struct playlist_provider * -cue_playlist_open_stream(struct input_stream *is) -{ - struct cue_playlist *playlist = g_new(struct cue_playlist, 1); - playlist_provider_init(&playlist->base, &cue_playlist_plugin); - - playlist->is = is; - playlist->tis = text_input_stream_new(is); - playlist->parser = cue_parser_new(); - - - return &playlist->base; -} - -static void -cue_playlist_close(struct playlist_provider *_playlist) -{ - struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - - cue_parser_free(playlist->parser); - text_input_stream_free(playlist->tis); - g_free(playlist); -} - -static struct song * -cue_playlist_read(struct playlist_provider *_playlist) -{ - struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - - struct song *song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - - const char *line; - while ((line = text_input_stream_read(playlist->tis)) != NULL) { - cue_parser_feed(playlist->parser, line); - song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - } - - cue_parser_finish(playlist->parser); - return cue_parser_get(playlist->parser); -} - -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_stream = cue_playlist_open_stream, - .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 deleted file mode 100644 index c02e2235a..000000000 --- a/src/playlist/cue_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c deleted file mode 100644 index 30b852c73..000000000 --- a/src/playlist/despotify_playlist_plugin.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/despotify_playlist_plugin.h" -#include "playlist_plugin.h" -#include "playlist_list.h" -#include "conf.h" -#include "uri.h" -#include "tag.h" -#include "song.h" -#include "input_stream.h" -#include "despotify_utils.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> -#include <despotify.h> - -struct despotify_playlist { - struct playlist_provider base; - - struct despotify_session *session; - GSList *list; -}; - -static void -add_song(struct despotify_playlist *ctx, struct ds_track *track) -{ - const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; - struct song *song; - char uri[128]; - char *ds_uri; - - /* Create a spt://... URI for MPD */ - g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme); - ds_uri = uri + strlen(dsp_scheme) + 3; - - if (despotify_track_to_uri(track, ds_uri) != ds_uri) { - /* Should never really fail, but let's be sure */ - g_debug("Can't add track %s\n", track->title); - return; - } - - song = song_remote_new(uri); - song->tag = mpd_despotify_tag_from_track(track); - - ctx->list = g_slist_prepend(ctx->list, song); -} - -static bool -parse_track(struct despotify_playlist *ctx, - struct ds_link *link) -{ - struct ds_track *track; - - track = despotify_link_get_track(ctx->session, link); - if (!track) - return false; - add_song(ctx, track); - - return true; -} - -static bool -parse_playlist(struct despotify_playlist *ctx, - struct ds_link *link) -{ - struct ds_playlist *playlist; - struct ds_track *track; - - playlist = despotify_link_get_playlist(ctx->session, link); - if (!playlist) - return false; - - for (track = playlist->tracks; track; track = track->next) - add_song(ctx, track); - - return true; -} - -static bool -despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param) -{ - return true; -} - -static void -despotify_playlist_finish(void) -{ -} - - -static struct playlist_provider * -despotify_playlist_open_uri(const char *url, G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) -{ - struct despotify_playlist *ctx; - struct despotify_session *session; - struct ds_link *link; - bool parse_result; - - session = mpd_despotify_get_session(); - if (!session) - goto clean_none; - - /* Get link without spt:// */ - link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); - if (!link) { - g_debug("Can't find %s\n", url); - goto clean_none; - } - - ctx = g_new(struct despotify_playlist, 1); - - ctx->list = NULL; - ctx->session = session; - playlist_provider_init(&ctx->base, &despotify_playlist_plugin); - - switch (link->type) - { - case LINK_TYPE_TRACK: - parse_result = parse_track(ctx, link); - break; - case LINK_TYPE_PLAYLIST: - parse_result = parse_playlist(ctx, link); - break; - default: - parse_result = false; - break; - } - despotify_free_link(link); - if (!parse_result) - goto clean_playlist; - - ctx->list = g_slist_reverse(ctx->list); - - return &ctx->base; - -clean_playlist: - g_slist_free(ctx->list); -clean_none: - - return NULL; -} - -static void -track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct song *song = (struct song *)data; - - song_free(song); -} - -static void -despotify_playlist_close(struct playlist_provider *_playlist) -{ - struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; - - g_slist_foreach(ctx->list, track_free_callback, NULL); - g_slist_free(ctx->list); - - g_free(ctx); -} - - -static struct song * -despotify_playlist_read(struct playlist_provider *_playlist) -{ - struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; - struct song *out; - - if (!ctx->list) - return NULL; - - /* Remove the current track */ - out = ctx->list->data; - ctx->list = g_slist_remove(ctx->list, out); - - return out; -} - - -static const char *const despotify_schemes[] = { - "spt", - NULL -}; - -const struct playlist_plugin despotify_playlist_plugin = { - .name = "despotify", - - .init = despotify_playlist_init, - .finish = despotify_playlist_finish, - .open_uri = despotify_playlist_open_uri, - .read = despotify_playlist_read, - .close = despotify_playlist_close, - - .schemes = despotify_schemes, -}; diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/despotify_playlist_plugin.h deleted file mode 100644 index f8ee20de0..000000000 --- a/src/playlist/despotify_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin despotify_playlist_plugin; - -#endif diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/embcue_playlist_plugin.c deleted file mode 100644 index 6d9a957f9..000000000 --- a/src/playlist/embcue_playlist_plugin.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Playlist plugin that reads embedded cue sheets from the "CUESHEET" - * tag of a music file. - */ - -#include "config.h" -#include "playlist/embcue_playlist_plugin.h" -#include "playlist_plugin.h" -#include "tag.h" -#include "tag_handler.h" -#include "tag_file.h" -#include "tag_ape.h" -#include "tag_id3.h" -#include "song.h" -#include "cue/cue_parser.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "cue" - -struct embcue_playlist { - struct playlist_provider base; - - /** - * This is an override for the CUE's "FILE". An embedded CUE - * sheet must always point to the song file it is contained - * in. - */ - char *filename; - - /** - * The value of the file's "CUESHEET" tag. - */ - char *cuesheet; - - /** - * The offset of the next line within "cuesheet". - */ - char *next; - - struct cue_parser *parser; -}; - -static void -embcue_tag_pair(const char *name, const char *value, void *ctx) -{ - struct embcue_playlist *playlist = ctx; - - if (playlist->cuesheet == NULL && - g_ascii_strcasecmp(name, "cuesheet") == 0) - playlist->cuesheet = g_strdup(value); -} - -static const struct tag_handler embcue_tag_handler = { - .pair = embcue_tag_pair, -}; - -static struct playlist_provider * -embcue_playlist_open_uri(const char *uri, - G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) -{ - if (!g_path_is_absolute(uri)) - /* only local files supported */ - return NULL; - - struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1); - playlist_provider_init(&playlist->base, &embcue_playlist_plugin); - playlist->cuesheet = NULL; - - tag_file_scan(uri, &embcue_tag_handler, playlist); - if (playlist->cuesheet == NULL) { - tag_ape_scan2(uri, &embcue_tag_handler, playlist); - if (playlist->cuesheet == NULL) - tag_id3_scan(uri, &embcue_tag_handler, playlist); - } - - if (playlist->cuesheet == NULL) { - /* no "CUESHEET" tag found */ - g_free(playlist); - return NULL; - } - - playlist->filename = g_path_get_basename(uri); - - playlist->next = playlist->cuesheet; - playlist->parser = cue_parser_new(); - - return &playlist->base; -} - -static void -embcue_playlist_close(struct playlist_provider *_playlist) -{ - struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; - - cue_parser_free(playlist->parser); - g_free(playlist->cuesheet); - g_free(playlist->filename); - g_free(playlist); -} - -static struct song * -embcue_playlist_read(struct playlist_provider *_playlist) -{ - struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; - - struct song *song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - - while (*playlist->next != 0) { - const char *line = playlist->next; - char *eol = strpbrk(playlist->next, "\r\n"); - if (eol != NULL) { - /* null-terminate the line */ - *eol = 0; - playlist->next = eol + 1; - } else - /* last line; put the "next" pointer to the - end of the buffer */ - playlist->next += strlen(line); - - cue_parser_feed(playlist->parser, line); - song = cue_parser_get(playlist->parser); - if (song != NULL) - return song_replace_uri(song, playlist->filename); - } - - cue_parser_finish(playlist->parser); - song = cue_parser_get(playlist->parser); - if (song != NULL) - song = song_replace_uri(song, playlist->filename); - return song; -} - -static const char *const embcue_playlist_suffixes[] = { - /* a few codecs that are known to be supported; there are - probably many more */ - "flac", - "mp3", "mp2", - "mp4", "mp4a", "m4b", - "ape", - "wv", - "ogg", "oga", - NULL -}; - -const struct playlist_plugin embcue_playlist_plugin = { - .name = "cue", - - .open_uri = embcue_playlist_open_uri, - .close = embcue_playlist_close, - .read = embcue_playlist_read, - - .suffixes = embcue_playlist_suffixes, - .mime_types = NULL, -}; diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/embcue_playlist_plugin.h deleted file mode 100644 index c5f21b27e..000000000 --- a/src/playlist/embcue_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin embcue_playlist_plugin; - -#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c deleted file mode 100644 index 19be8d1c4..000000000 --- a/src/playlist/extm3u_playlist_plugin.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/extm3u_playlist_plugin.h" -#include "playlist_plugin.h" -#include "text_input_stream.h" -#include "uri.h" -#include "song.h" -#include "tag.h" -#include "string_util.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 = strchug_fast_c(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 deleted file mode 100644 index 5f611ac9c..000000000 --- a/src/playlist/extm3u_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c deleted file mode 100644 index ead14deaa..000000000 --- a/src/playlist/lastfm_playlist_plugin.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/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.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 (strlen(passwd) != 32) - lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, - passwd, strlen(passwd)); - else - 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, GMutex *mutex, GCond *cond) -{ - struct input_stream *input_stream; - GError *error = NULL; - char buffer[4096]; - size_t length = 0, nbytes; - - input_stream = input_stream_open(url, mutex, cond, &error); - if (input_stream == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - g_mutex_lock(mutex); - - input_stream_wait_ready(input_stream); - - 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 */ - g_mutex_unlock(mutex); - input_stream_close(input_stream); - return NULL; - } - - length += nbytes; - } while (length < sizeof(buffer)); - - g_mutex_unlock(mutex); - - 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 response 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, GMutex *mutex, GCond *cond) -{ - 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, mutex, cond); - 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, mutex, cond); - 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, mutex, cond, &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; - } - - g_mutex_lock(mutex); - - input_stream_wait_ready(playlist->is); - - /* last.fm does not send a MIME type, we have to fake it here - :-( */ - g_free(playlist->is->mime); - playlist->is->mime = g_strdup("application/xspf+xml"); - - g_mutex_unlock(mutex); - - /* parse the XSPF playlist */ - - playlist->xspf = playlist_list_open_stream(playlist->is, NULL); - 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 deleted file mode 100644 index 46a8b0caf..000000000 --- a/src/playlist/lastfm_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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 deleted file mode 100644 index 45b70d2b1..000000000 --- a/src/playlist/m3u_playlist_plugin.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/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 deleted file mode 100644 index 3890a5fc2..000000000 --- a/src/playlist/m3u_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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 deleted file mode 100644 index c4e5492af..000000000 --- a/src/playlist/pls_playlist_plugin.c +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/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_lock_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 deleted file mode 100644 index d03435f6d..000000000 --- a/src/playlist/pls_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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 deleted file mode 100644 index 6740cba7e..000000000 --- a/src/playlist/rss_playlist_plugin.c +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/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_lock_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 deleted file mode 100644 index 3b376de79..000000000 --- a/src/playlist/rss_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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/soundcloud_playlist_plugin.c b/src/playlist/soundcloud_playlist_plugin.c deleted file mode 100644 index 7c79f880a..000000000 --- a/src/playlist/soundcloud_playlist_plugin.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/soundcloud_playlist_plugin.h" -#include "conf.h" -#include "input_stream.h" -#include "playlist_plugin.h" -#include "song.h" -#include "tag.h" - -#include <glib.h> -#include <yajl/yajl_parse.h> - -#include <string.h> - -struct soundcloud_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static struct { - char *apikey; -} soundcloud_config; - -static bool -soundcloud_init(const struct config_param *param) -{ - soundcloud_config.apikey = - config_dup_block_string(param, "apikey", NULL); - if (soundcloud_config.apikey == NULL) { - g_debug("disabling the soundcloud playlist plugin " - "because API key is not set"); - return false; - } - - return true; -} - -static void -soundcloud_finish(void) -{ - g_free(soundcloud_config.apikey); -} - -/** - * Construct a full soundcloud resolver URL from the given fragment. - * @param uri uri of a soundcloud page (or just the path) - * @return Constructed URL. Must be freed with g_free. - */ -static char * -soundcloud_resolve(const char* uri) { - char *u, *ru; - - if (g_str_has_prefix(uri, "http://")) { - u = g_strdup(uri); - } else if (g_str_has_prefix(uri, "soundcloud.com")) { - u = g_strconcat("http://", uri, NULL); - } else { - /* assume it's just a path on soundcloud.com */ - u = g_strconcat("http://soundcloud.com/", uri, NULL); - } - - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", - u, "&client_id=", soundcloud_config.apikey, NULL); - g_free(u); - - return ru; -} - -/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ - -enum key { - Duration, - Title, - Stream_URL, - Other, -}; - -const char* key_str[] = { - "duration", - "title", - "stream_url", - NULL, -}; - -struct parse_data { - int key; - char* stream_url; - long duration; - char* title; - int got_url; /* nesting level of last stream_url */ - GSList* songs; -}; - -static int handle_integer(void *ctx, - long -#ifndef HAVE_YAJL1 - long -#endif - intval) -{ - struct parse_data *data = (struct parse_data *) ctx; - - switch (data->key) { - case Duration: - data->duration = intval; - break; - default: - break; - } - - return 1; -} - -static int handle_string(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - const char *s = (const char *) stringval; - - switch (data->key) { - case Title: - if (data->title != NULL) - g_free(data->title); - data->title = g_strndup(s, stringlen); - break; - case Stream_URL: - if (data->stream_url != NULL) - g_free(data->stream_url); - data->stream_url = g_strndup(s, stringlen); - data->got_url = 1; - break; - default: - break; - } - - return 1; -} - -static int handle_mapkey(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - - int i; - data->key = Other; - - for (i = 0; i < Other; ++i) { - if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { - data->key = i; - break; - } - } - - return 1; -} - -static int handle_start_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 0) - data->got_url++; - - return 1; -} - -static int handle_end_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 1) { - data->got_url--; - return 1; - } - - if (data->got_url == 0) - return 1; - - /* got_url == 1, track finished, make it into a song */ - data->got_url = 0; - - struct song *s; - struct tag *t; - char *u; - - u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); - s = song_remote_new(u); - g_free(u); - t = tag_new(); - t->time = data->duration / 1000; - if (data->title != NULL) - tag_add_item(t, TAG_NAME, data->title); - s->tag = t; - - data->songs = g_slist_prepend(data->songs, s); - - return 1; -} - -static yajl_callbacks parse_callbacks = { - NULL, - NULL, - handle_integer, - NULL, - NULL, - handle_string, - handle_start_map, - handle_mapkey, - handle_end_map, - NULL, - NULL, -}; - -/** - * Read JSON data and parse it using the given YAJL parser. - * @param url URL of the JSON data. - * @param hand YAJL parser handle. - * @return -1 on error, 0 on success. - */ -static int -soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond) -{ - struct input_stream *input_stream; - GError *error = NULL; - char buffer[4096]; - unsigned char *ubuffer = (unsigned char *)buffer; - size_t nbytes; - - input_stream = input_stream_open(url, mutex, cond, &error); - if (input_stream == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - return -1; - } - - g_mutex_lock(mutex); - input_stream_wait_ready(input_stream); - - yajl_status stat; - int done = 0; - - while (!done) { - nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error); - if (nbytes == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - if (input_stream_eof(input_stream)) { - done = true; - } else { - g_mutex_unlock(mutex); - input_stream_close(input_stream); - return -1; - } - } - - if (done) { -#ifdef HAVE_YAJL1 - stat = yajl_parse_complete(hand); -#else - stat = yajl_complete_parse(hand); -#endif - } else - stat = yajl_parse(hand, ubuffer, nbytes); - - if (stat != yajl_status_ok -#ifdef HAVE_YAJL1 - && stat != yajl_status_insufficient_data -#endif - ) - { - unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); - g_warning("%s", str); - yajl_free_error(hand, str); - break; - } - } - - g_mutex_unlock(mutex); - input_stream_close(input_stream); - - return 0; -} - -/** - * Parse a soundcloud:// URL and create a playlist. - * @param uri A soundcloud URL. Accepted forms: - * soundcloud://track/<track-id> - * soundcloud://playlist/<playlist-id> - * soundcloud://url/<url or path of soundcloud page> - */ - -static struct playlist_provider * -soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct soundcloud_playlist *playlist = NULL; - - char *s, *p; - char *scheme, *arg, *rest; - s = g_strdup(uri); - scheme = s; - for (p = s; *p; p++) { - if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { - *p = 0; - p += 3; - break; - } - } - arg = p; - for (; *p; p++) { - if (*p == '/') { - *p = 0; - p++; - break; - } - } - rest = p; - - if (strcmp(scheme, "soundcloud") != 0) { - g_warning("incompatible scheme for soundcloud plugin: %s", scheme); - g_free(s); - return NULL; - } - - char *u = NULL; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", - rest, ".json?client_id=", soundcloud_config.apikey, NULL); - } else if (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", - rest, ".json?client_id=", soundcloud_config.apikey, NULL); - } else if (strcmp(arg, "url") == 0) { - /* Translate to soundcloud resolver call. libcurl will automatically - follow the redirect to the right resource. */ - u = soundcloud_resolve(rest); - } - g_free(s); - - if (u == NULL) { - g_warning("unknown soundcloud URI"); - return NULL; - } - - yajl_handle hand; - struct parse_data data; - - data.got_url = 0; - data.songs = NULL; - data.title = NULL; - data.stream_url = NULL; -#ifdef HAVE_YAJL1 - hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); -#else - hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); -#endif - - int ret = soundcloud_parse_json(u, hand, mutex, cond); - - g_free(u); - yajl_free(hand); - if (data.title != NULL) - g_free(data.title); - if (data.stream_url != NULL) - g_free(data.stream_url); - - if (ret == -1) - return NULL; - - playlist = g_new(struct soundcloud_playlist, 1); - playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin); - playlist->songs = g_slist_reverse(data.songs); - - return &playlist->base; -} - -static void -soundcloud_close(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - g_free(playlist); -} - - -static struct song * -soundcloud_read(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - if (playlist->songs == NULL) - return NULL; - - struct song* s; - s = (struct song *)playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, s); - return s; -} - -static const char *const soundcloud_schemes[] = { - "soundcloud", - NULL -}; - -const struct playlist_plugin soundcloud_playlist_plugin = { - .name = "soundcloud", - - .init = soundcloud_init, - .finish = soundcloud_finish, - .open_uri = soundcloud_open_uri, - .close = soundcloud_close, - .read = soundcloud_read, - - .schemes = soundcloud_schemes, -}; - - diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/soundcloud_playlist_plugin.h deleted file mode 100644 index e09e2dd46..000000000 --- a/src/playlist/soundcloud_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin soundcloud_playlist_plugin; - -#endif diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c deleted file mode 100644 index 17d9040e2..000000000 --- a/src/playlist/xspf_playlist_plugin.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/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_lock_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 deleted file mode 100644 index 4636d7e83..000000000 --- a/src/playlist/xspf_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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 deleted file mode 100644 index 450ca5932..000000000 --- a/src/playlist_any.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_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, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - assert(uri_has_scheme(uri)); - - struct playlist_provider *playlist = - playlist_list_open_uri(uri, mutex, cond); - if (playlist != NULL) { - *is_r = NULL; - return playlist; - } - - GError *error = NULL; - struct input_stream *is = input_stream_open(uri, mutex, cond, &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, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - return uri_has_scheme(uri) - ? playlist_open_remote(uri, mutex, cond, is_r) - : playlist_mapper_open(uri, mutex, cond, is_r); -} diff --git a/src/playlist_any.h b/src/playlist_any.h deleted file mode 100644 index 310913de9..000000000 --- a/src/playlist_any.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_ANY_H -#define MPD_PLAYLIST_ANY_H - -#include <glib.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, GMutex *mutex, GCond *cond, - struct input_stream **is_r); - -#endif diff --git a/src/playlist_control.c b/src/playlist_control.c deleted file mode 100644 index 0dea7676a..000000000 --- a/src/playlist_control.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for controlling playback on the playlist level. - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "idle.h" - -#include <glib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_stop(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - g_debug("stop"); - pc_stop(pc); - playlist->queued = -1; - playlist->playing = false; - - if (playlist->queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } -} - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song) -{ - unsigned i = song; - - pc_clear_error(pc); - - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue_is_empty(&playlist->queue)) - return PLAYLIST_RESULT_SUCCESS; - - if (playlist->playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc_set_pause(pc, false); - return PLAYLIST_RESULT_SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = playlist->current >= 0 - ? playlist->current - : 0; - } else if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (playlist->queue.random) { - if (song >= 0) - /* "i" is currently the song position (which - would be equal to the order number in - no-random mode); convert it to a order - number, because random mode is enabled */ - i = queue_position_to_order(&playlist->queue, song); - - if (!playlist->playing) - playlist->current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue_swap_order(&playlist->queue, - i, playlist->current); - i = playlist->current; - } - - playlist->stop_on_error = false; - playlist->error_count = 0; - - playlist_play_order(playlist, pc, i); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int id) -{ - int song; - - if (id == -1) { - return playlist_play(playlist, pc, id); - } - - song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_play(playlist, pc, song); -} - -void -playlist_next(struct playlist *playlist, struct player_control *pc) -{ - int next_order; - int current; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert(queue_valid_order(&playlist->queue, playlist->current)); - - current = playlist->current; - playlist->stop_on_error = false; - - /* determine the next song from the queue's order list */ - - next_order = queue_next_order(&playlist->queue, playlist->current); - if (next_order < 0) { - /* no song after this one: stop playback */ - playlist_stop(playlist, pc); - - /* reset "current song" */ - playlist->current = -1; - } - else - { - if (next_order == 0 && playlist->queue.random) { - /* The queue told us that the next song is the first - song. This means we are in repeat mode. Shuffle - the queue order, so this time, the user hears the - songs in a different than before */ - assert(playlist->queue.repeat); - - queue_shuffle_order(&playlist->queue); - - /* note that playlist->current and playlist->queued are - now invalid, but playlist_play_order() will - discard them anyway */ - } - - playlist_play_order(playlist, pc, next_order); - } - - /* Consume mode removes each played songs. */ - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); -} - -void -playlist_previous(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(queue_length(&playlist->queue) > 0); - - if (playlist->current > 0) { - /* play the preceding song */ - playlist_play_order(playlist, pc, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playlist_play_order(playlist, pc, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playlist_play_order(playlist, pc, playlist->current); - } -} - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time) -{ - const struct song *queued; - unsigned i; - bool success; - - if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - if (playlist->queue.random) - i = queue_position_to_order(&playlist->queue, song); - else - i = song; - - pc_clear_error(pc); - playlist->stop_on_error = true; - playlist->error_count = 0; - - if (!playlist->playing || (unsigned)playlist->current != i) { - /* seeking is not within the current song - prepare - song change */ - - playlist->playing = true; - playlist->current = i; - - queued = NULL; - } - - success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); - if (!success) { - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_NOT_PLAYING; - } - - playlist->queued = -1; - playlist_update_queued_song(playlist, pc, NULL); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_seek_song(playlist, pc, song, seek_time); -} - -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative) -{ - if (!playlist->playing) - return PLAYLIST_RESULT_NOT_PLAYING; - - if (relative) { - struct player_status status; - pc_get_status(pc, &status); - - if (status.state != PLAYER_STATE_PLAY && - status.state != PLAYER_STATE_PAUSE) - return PLAYLIST_RESULT_NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return playlist_seek_song(playlist, pc, playlist->current, seek_time); -} diff --git a/src/playlist_database.c b/src/playlist_database.c deleted file mode 100644 index 6b9d87155..000000000 --- a/src/playlist_database.c +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_database.h" -#include "playlist_vector.h" -#include "text_file.h" -#include "string_util.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 list_head *pv) -{ - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) - 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 list_head *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 = strchug_fast_c(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 deleted file mode 100644 index 3238fa06b..000000000 --- a/src/playlist_database.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_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 list_head; - -void -playlist_vector_save(FILE *fp, const struct list_head *pv); - -bool -playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, - GString *buffer, GError **error_r); - -#endif diff --git a/src/playlist_edit.c b/src/playlist_edit.c deleted file mode 100644 index 8042f2f76..000000000 --- a/src/playlist_edit.c +++ /dev/null @@ -1,492 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Functions for editing the playlist (adding, removing, reordering - * songs in the queue). - * - */ - -#include "config.h" -#include "playlist_internal.h" -#include "player_control.h" -#include "database.h" -#include "uri.h" -#include "song.h" -#include "idle.h" - -#include <stdlib.h> - -static void playlist_increment_version(struct playlist *playlist) -{ - queue_increment_version(&playlist->queue); - - idle_add(IDLE_PLAYLIST); -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc) -{ - playlist_stop(playlist, pc); - - /* make sure there are no references to allocated songs - anymore */ - for (unsigned i = 0; i < queue_length(&playlist->queue); i++) { - const struct song *song = queue_get(&playlist->queue, i); - if (!song_in_database(song)) - pc_song_deleted(pc, song); - } - - queue_clear(&playlist->queue); - - playlist->current = -1; - - playlist_increment_version(playlist); -} - -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id) -{ - struct song *song = song_file_load(path_fs, NULL); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_append_song(playlist, pc, song, added_id); -} - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id) -{ - const struct song *queued; - unsigned id; - - if (queue_is_full(&playlist->queue)) - return PLAYLIST_RESULT_TOO_LARGE; - - queued = playlist_get_queued_song(playlist); - - id = queue_append(&playlist->queue, song, 0); - - if (playlist->queue.random) { - /* shuffle the new song into the list of remaining - songs to play */ - - unsigned start; - if (playlist->queued >= 0) - start = playlist->queued + 1; - else - start = playlist->current + 1; - if (start < queue_length(&playlist->queue)) - queue_shuffle_order_last(&playlist->queue, start, - queue_length(&playlist->queue)); - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - if (added_id) - *added_id = id; - - 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, struct player_control *pc, - 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, pc, song, added_id); -} - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2) -{ - const struct song *queued; - - if (!queue_valid_position(&playlist->queue, song1) || - !queue_valid_position(&playlist->queue, song2)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - queue_swap(&playlist->queue, song1, song2); - - if (playlist->queue.random) { - /* update the queue order, so that playlist->current - still points to the current song order */ - - queue_swap_order(&playlist->queue, - queue_position_to_order(&playlist->queue, - song1), - queue_position_to_order(&playlist->queue, - song2)); - } else { - /* correct the "current" song order */ - - if (playlist->current == (int)song1) - playlist->current = song2; - else if (playlist->current == (int)song2) - playlist->current = song1; - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2) -{ - int song1 = queue_id_to_position(&playlist->queue, id1); - int song2 = queue_id_to_position(&playlist->queue, id2); - - if (song1 < 0 || song2 < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_swap_songs(playlist, pc, song1, song2); -} - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, - uint8_t priority) -{ - if (start >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (end > queue_length(&playlist->queue)) - end = queue_length(&playlist->queue); - - if (start >= end) - return PLAYLIST_RESULT_SUCCESS; - - /* remember "current" and "queued" */ - - int current_position = playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - const struct song *queued = playlist_get_queued_song(playlist); - - /* apply the priority changes */ - - queue_set_priority_range(&playlist->queue, start, end, priority, - playlist->current); - - playlist_increment_version(playlist); - - /* restore "current" and choose a new "queued" */ - - if (current_position >= 0) - playlist->current = queue_position_to_order(&playlist->queue, - current_position); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority) -{ - int song_position = queue_id_to_position(&playlist->queue, song_id); - if (song_position < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_set_priority(playlist, pc, - song_position, song_position + 1, - priority); - -} - -static void -playlist_delete_internal(struct playlist *playlist, struct player_control *pc, - unsigned song, const struct song **queued_p) -{ - unsigned songOrder; - - assert(song < queue_length(&playlist->queue)); - - songOrder = queue_position_to_order(&playlist->queue, song); - - if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE; - - /* the current song is going to be deleted: stop the player */ - - pc_stop(pc); - playlist->playing = false; - - /* see which song is going to be played instead */ - - playlist->current = queue_next_order(&playlist->queue, - playlist->current); - if (playlist->current == (int)songOrder) - playlist->current = -1; - - if (playlist->current >= 0 && !paused) - /* play the song after the deleted one */ - playlist_play_order(playlist, pc, playlist->current); - else - /* no songs left to play, stop playback - completely */ - playlist_stop(playlist, pc); - - *queued_p = NULL; - } else if (playlist->current == (int)songOrder) - /* there's a "current song" but we're not playing - currently - clear "current" */ - playlist->current = -1; - - /* now do it: remove the song */ - - if (!song_in_database(queue_get(&playlist->queue, song))) - pc_song_deleted(pc, queue_get(&playlist->queue, song)); - - queue_delete(&playlist->queue, song); - - /* update the "current" and "queued" variables */ - - if (playlist->current > (int)songOrder) { - playlist->current--; - } -} - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - 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, pc, song, &queued); - - playlist_increment_version(playlist); - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - 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, pc, --end, &queued); - } while (end != start); - - playlist_increment_version(playlist); - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned id) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_delete(playlist, pc, song); -} - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song) -{ - for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) - if (song == queue_get(&playlist->queue, i)) - playlist_delete(playlist, pc, i); - - pc_song_deleted(pc, song); -} - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to) -{ - const struct song *queued; - int currentSong; - - if (!queue_valid_position(&playlist->queue, start) || - !queue_valid_position(&playlist->queue, end - 1)) - return PLAYLIST_RESULT_BAD_RANGE; - - if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) || - (to < 0 && abs(to) > (int)queue_length(&playlist->queue))) - return PLAYLIST_RESULT_BAD_RANGE; - - if ((int)start == to) - /* nothing happens */ - return PLAYLIST_RESULT_SUCCESS; - - queued = playlist_get_queued_song(playlist); - - /* - * (to < 0) => move to offset from current song - * (-playlist.length == to) => move to position BEFORE current song - */ - currentSong = playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - if (to < 0) { - if (currentSong < 0) - /* can't move relative to current song, - because there is no current song */ - return PLAYLIST_RESULT_BAD_RANGE; - - if (start <= (unsigned)currentSong && (unsigned)currentSong < end) - /* no-op, can't be moved to offset of itself */ - return PLAYLIST_RESULT_SUCCESS; - to = (currentSong + abs(to)) % queue_length(&playlist->queue); - if (start < (unsigned)to) - to--; - } - - queue_move_range(&playlist->queue, start, end, to); - - if (!playlist->queue.random) { - /* update current/queued */ - if ((int)start <= playlist->current && - (unsigned)playlist->current < end) - playlist->current += to - start; - else if (playlist->current >= (int)end && - playlist->current <= to) { - playlist->current -= end - start; - } else if (playlist->current >= to && - playlist->current < (int)start) { - playlist->current += end - start; - } - } - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, int to) -{ - int song = queue_id_to_position(&playlist->queue, id1); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_move_range(playlist, pc, song, song+1, to); -} - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end) -{ - const struct song *queued; - - if (end > queue_length(&playlist->queue)) - /* correct the "end" offset */ - end = queue_length(&playlist->queue); - - if ((start+1) >= end) - /* needs at least two entries. */ - return; - - queued = playlist_get_queued_song(playlist); - if (playlist->playing && playlist->current >= 0) { - unsigned current_position; - current_position = queue_order_to_position(&playlist->queue, - playlist->current); - - if (current_position >= start && current_position < end) - { - /* put current playing song first */ - queue_swap(&playlist->queue, start, current_position); - - if (playlist->queue.random) { - playlist->current = - queue_position_to_order(&playlist->queue, start); - } else - playlist->current = start; - - /* start shuffle after the current song */ - start++; - } - } else { - /* no playback currently: reset playlist->current */ - - playlist->current = -1; - } - - queue_shuffle_range(&playlist->queue, start, end); - - playlist_increment_version(playlist); - - playlist_update_queued_song(playlist, pc, queued); -} diff --git a/src/playlist_error.h b/src/playlist_error.h deleted file mode 100644 index ad9c62cf1..000000000 --- a/src/playlist_error.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_ERROR_H -#define MPD_PLAYLIST_ERROR_H - -#include <glib.h> - -enum playlist_result { - PLAYLIST_RESULT_SUCCESS, - PLAYLIST_RESULT_ERRNO, - PLAYLIST_RESULT_DENIED, - PLAYLIST_RESULT_NO_SUCH_SONG, - PLAYLIST_RESULT_NO_SUCH_LIST, - PLAYLIST_RESULT_LIST_EXISTS, - PLAYLIST_RESULT_BAD_NAME, - PLAYLIST_RESULT_BAD_RANGE, - PLAYLIST_RESULT_NOT_PLAYING, - PLAYLIST_RESULT_TOO_LARGE, - PLAYLIST_RESULT_DISABLED, -}; - -/** - * Quark for GError.domain; the code is an enum #playlist_result. - */ -G_GNUC_CONST -static inline GQuark -playlist_quark(void) -{ - return g_quark_from_static_string("playlist"); -} - -#endif diff --git a/src/playlist_global.c b/src/playlist_global.c deleted file mode 100644 index 650b88bb8..000000000 --- a/src/playlist_global.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * The manager of the global "struct playlist" instance (g_playlist). - * - */ - -#include "config.h" -#include "playlist.h" -#include "playlist_state.h" -#include "event_pipe.h" -#include "main.h" - -struct playlist g_playlist; - -static void -playlist_tag_event(void) -{ - playlist_tag_changed(&g_playlist); -} - -static void -playlist_event(void) -{ - playlist_sync(&g_playlist, global_player_control); -} - -void -playlist_global_init(void) -{ - playlist_init(&g_playlist); - - event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event); - event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); -} - -void -playlist_global_finish(void) -{ - playlist_finish(&g_playlist); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h deleted file mode 100644 index 81b175176..000000000 --- a/src/playlist_internal.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Internal header for the components of the playlist code. - * - */ - -#ifndef PLAYLIST_INTERNAL_H -#define PLAYLIST_INTERNAL_H - -#include "playlist.h" - -struct player_control; - -/** - * Returns the song object which is currently queued. Returns none if - * there is none (yet?) or if MPD isn't playing. - */ -const struct song * -playlist_get_queued_song(struct playlist *playlist); - -/** - * Updates the "queued song". Calculates the next song according to - * the current one (if MPD isn't playing, it takes the first song), - * and queues this song. Clears the old queued song if there was one. - * - * @param prev the song which was previously queued, as determined by - * playlist_get_queued_song() - */ -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev); - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum); - -#endif diff --git a/src/playlist_list.c b/src/playlist_list.c deleted file mode 100644 index 68d24fe49..000000000 --- a/src/playlist_list.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_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/despotify_playlist_plugin.h" -#include "playlist/soundcloud_playlist_plugin.h" -#include "playlist/pls_playlist_plugin.h" -#include "playlist/asx_playlist_plugin.h" -#include "playlist/rss_playlist_plugin.h" -#include "playlist/cue_playlist_plugin.h" -#include "playlist/embcue_playlist_plugin.h" -#include "input_stream.h" -#include "uri.h" -#include "string_util.h" -#include "conf.h" -#include "mpd_error.h" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -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_DESPOTIFY - &despotify_playlist_plugin, -#endif -#ifdef ENABLE_LASTFM - &lastfm_playlist_plugin, -#endif -#ifdef ENABLE_SOUNDCLOUD - &soundcloud_playlist_plugin, -#endif - &cue_playlist_plugin, - &embcue_playlist_plugin, - NULL -}; - -/** which plugins have been initialized successfully? */ -static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; - -#define playlist_plugins_for_each_enabled(plugin) \ - playlist_plugins_for_each(plugin) \ - if (playlist_plugins_enabled[playlist_plugin_iterator - 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) -{ - playlist_plugins_for_each_enabled(plugin) - playlist_plugin_finish(plugin); -} - -static struct playlist_provider * -playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond, - 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, - mutex, cond); - if (playlist != NULL) - break; - - tried[i] = true; - } - } - - g_free(scheme); - return playlist; -} - -static struct playlist_provider * -playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond, - 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, - mutex, cond); - if (playlist != NULL) - break; - } - } - - return playlist; -} - -struct playlist_provider * -playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct playlist_provider *playlist; - /** this array tracks which plugins have already been tried by - 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, mutex, cond, tried); - if (playlist == NULL) - playlist = playlist_list_open_uri_suffix(uri, mutex, cond, - 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); - - playlist_plugins_for_each_enabled(plugin) { - if (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); - - playlist_plugins_for_each_enabled(plugin) { - if (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; - - input_stream_lock_wait_ready(is); - - 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); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix)) - return true; - } - - return false; -} - -struct playlist_provider * -playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, - 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, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - input_stream_lock_wait_ready(is); - - playlist = playlist_list_open_stream_suffix(is, suffix); - if (playlist != NULL) - *is_r = is; - else - input_stream_close(is); - - return playlist; -} diff --git a/src/playlist_list.h b/src/playlist_list.h deleted file mode 100644 index c3967d5ae..000000000 --- a/src/playlist_list.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_LIST_H -#define MPD_PLAYLIST_LIST_H - -#include <glib.h> - -#include <stdbool.h> - -struct playlist_provider; -struct input_stream; - -extern const struct playlist_plugin *const playlist_plugins[]; - -#define playlist_plugins_for_each(plugin) \ - for (const struct playlist_plugin *plugin, \ - *const*playlist_plugin_iterator = &playlist_plugins[0]; \ - (plugin = *playlist_plugin_iterator) != NULL; \ - ++playlist_plugin_iterator) - -/** - * 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, GMutex *mutex, GCond *cond); - -/** - * 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, GMutex *mutex, GCond *cond, - struct input_stream **is_r); - -#endif diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c deleted file mode 100644 index 13adb80d0..000000000 --- a/src/playlist_mapper.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_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, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - struct playlist_provider *playlist; - - playlist = playlist_list_open_uri(path_fs, mutex, cond); - if (playlist != NULL) - *is_r = NULL; - else - playlist = playlist_list_open_path(path_fs, mutex, cond, is_r); - - return playlist; -} - -/** - * Load a playlist from the configured playlist directory. - */ -static struct playlist_provider * -playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond, - 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, mutex, cond, 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, GMutex *mutex, GCond *cond, - 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, mutex, cond, is_r); - g_free(path_fs); - - return playlist; -} - -struct playlist_provider * -playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, - struct input_stream **is_r) -{ - struct playlist_provider *playlist; - - if (spl_valid_name(uri)) { - playlist = playlist_open_in_playlist_dir(uri, mutex, cond, - is_r); - if (playlist != NULL) - return playlist; - } - - if (uri_safe_local(uri)) { - playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r); - if (playlist != NULL) - return playlist; - } - - return NULL; -} diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h deleted file mode 100644 index 9a7187d93..000000000 --- a/src/playlist_mapper.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_MAPPER_H -#define MPD_PLAYLIST_MAPPER_H - -#include <glib.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, GMutex *mutex, GCond *cond, - struct input_stream **is_r); - -#endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h deleted file mode 100644 index a27f651c0..000000000 --- a/src/playlist_plugin.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_PLUGIN_H - -#include <glib.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, - GMutex *mutex, GCond *cond); - - /** - * 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, - GMutex *mutex, GCond *cond) -{ - return plugin->open_uri(uri, mutex, cond); -} - -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 deleted file mode 100644 index 59c42f969..000000000 --- a/src/playlist_print.c +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_print.h" -#include "playlist_list.h" -#include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "queue_print.h" -#include "stored_playlist.h" -#include "song_print.h" -#include "song.h" -#include "database.h" -#include "client.h" -#include "input_stream.h" - -void -playlist_print_uris(struct client *client, const struct playlist *playlist) -{ - const struct queue *queue = &playlist->queue; - - queue_print_uris(client, queue, 0, queue_length(queue)); -} - -bool -playlist_print_info(struct client *client, const struct playlist *playlist, - unsigned start, unsigned end) -{ - const struct queue *queue = &playlist->queue; - - if (end > queue_length(queue)) - /* correct the "end" offset */ - end = queue_length(queue); - - if (start > end) - /* an invalid "start" offset is fatal */ - return false; - - queue_print_info(client, queue, start, end); - return true; -} - -bool -playlist_print_id(struct client *client, const struct playlist *playlist, - unsigned id) -{ - const struct queue *queue = &playlist->queue; - int position; - - position = queue_id_to_position(queue, id); - if (position < 0) - /* no such song */ - return false; - - return playlist_print_info(client, playlist, position, position + 1); -} - -bool -playlist_print_current(struct client *client, const struct playlist *playlist) -{ - int current_position = playlist_get_current_song(playlist); - - if (current_position < 0) - return false; - - queue_print_info(client, &playlist->queue, - current_position, current_position + 1); - return true; -} - -void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_find(client, &playlist->queue, list); -} - -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list) -{ - queue_search(client, &playlist->queue, list); -} - -void -playlist_print_changes_info(struct client *client, - const struct playlist *playlist, - uint32_t version) -{ - queue_print_changes_info(client, &playlist->queue, version); -} - -void -playlist_print_changes_position(struct client *client, - const struct playlist *playlist, - uint32_t version) -{ - queue_print_changes_position(client, &playlist->queue, version); -} - -bool -spl_print(struct client *client, const char *name_utf8, bool detail, - GError **error_r) -{ - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) - return false; - - for (unsigned i = 0; i < list->len; ++i) { - const char *temp = g_ptr_array_index(list, i); - bool wrote = false; - - if (detail) { - struct song *song = db_get_song(temp); - if (song) { - song_print_info(client, song); - wrote = true; - } - } - - if (!wrote) { - client_printf(client, SONG_FILE "%s\n", temp); - } - } - - 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, false); - if (song == NULL) - continue; - - if (detail) - song_print_info(client, song); - else - song_print_uri(client, song); - - if (!song_in_database(song)) - song_free(song); - } - - g_free(base_uri); -} - -bool -playlist_file_print(struct client *client, const char *uri, bool detail) -{ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is; - struct playlist_provider *playlist = - playlist_open_any(uri, mutex, cond, &is); - if (playlist == NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - return false; - } - - playlist_provider_print(client, uri, playlist, detail); - playlist_plugin_close(playlist); - - if (is != NULL) - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - - return true; -} diff --git a/src/playlist_print.h b/src/playlist_print.h deleted file mode 100644 index d4f1911d2..000000000 --- a/src/playlist_print.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef PLAYLIST_PRINT_H -#define PLAYLIST_PRINT_H - -#include <glib.h> -#include <stdbool.h> -#include <stdint.h> - -struct client; -struct playlist; -struct locate_item_list; - -/** - * Sends the whole playlist to the client, song URIs only. - */ -void -playlist_print_uris(struct client *client, const struct playlist *playlist); - -/** - * Sends a range of the playlist to the client, including all known - * information about the songs. The "end" offset is decreased - * automatically if it is too large; passing UINT_MAX is allowed. - * This function however fails when the start offset is invalid. - */ -bool -playlist_print_info(struct client *client, const struct playlist *playlist, - unsigned start, unsigned end); - -/** - * Sends the song with the specified id to the client. - * - * @return true on suite, false if there is no such song - */ -bool -playlist_print_id(struct client *client, const struct playlist *playlist, - unsigned id); - -/** - * Sends the current song to the client. - * - * @return true on success, false if there is no current song - */ -bool -playlist_print_current(struct client *client, const struct playlist *playlist); - -/** - * Find songs in the playlist. - */ -void -playlist_print_find(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** - * Search for songs in the playlist. - */ -void -playlist_print_search(struct client *client, const struct playlist *playlist, - const struct locate_item_list *list); - -/** - * Print detailed changes since the specified playlist version. - */ -void -playlist_print_changes_info(struct client *client, - const struct playlist *playlist, - uint32_t version); - -/** - * Print changes since the specified playlist version, position only. - */ -void -playlist_print_changes_position(struct client *client, - const struct playlist *playlist, - uint32_t version); - -/** - * Send the stored playlist to the client. - * - * @param client the client which requested the playlist - * @param name_utf8 the name of the stored playlist 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 -spl_print(struct client *client, const char *name_utf8, bool detail, - GError **error_r); - -/** - * 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 deleted file mode 100644 index aada94984..000000000 --- a/src/playlist_queue.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_queue.h" -#include "playlist_plugin.h" -#include "playlist_any.h" -#include "playlist_song.h" -#include "playlist.h" -#include "song.h" -#include "input_stream.h" - -enum playlist_result -playlist_load_into_queue(const char *uri, struct playlist_provider *source, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure) -{ - enum playlist_result result; - struct song *song; - char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; - - for (unsigned i = 0; - i < end_index && (song = playlist_plugin_read(source)) != NULL; - ++i) { - if (i < start_index) { - /* skip songs before the start index */ - if (!song_in_database(song)) - song_free(song); - continue; - } - - song = playlist_check_translate_song(song, base_uri, secure); - if (song == NULL) - continue; - - result = playlist_append_song(dest, pc, 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, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure) -{ - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is; - struct playlist_provider *playlist = - playlist_open_any(uri, mutex, cond, &is); - if (playlist == NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - return PLAYLIST_RESULT_NO_SUCH_LIST; - } - - enum playlist_result result = - playlist_load_into_queue(uri, playlist, start_index, end_index, - dest, pc, secure); - playlist_plugin_close(playlist); - - if (is != NULL) - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - - return result; -} diff --git a/src/playlist_queue.h b/src/playlist_queue.h deleted file mode 100644 index 24a851aab..000000000 --- a/src/playlist_queue.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/*! \file - * \brief Glue between playlist plugin and the play queue - */ - -#ifndef MPD_PLAYLIST_QUEUE_H -#define MPD_PLAYLIST_QUEUE_H - -#include "playlist_error.h" - -#include <stdbool.h> - -struct playlist_provider; -struct playlist; -struct player_control; - -/** - * Loads the contents of a playlist and append it to the specified - * play queue. - * - * @param uri the URI of the playlist, used to resolve relative song - * URIs - * @param start_index the index of the first song - * @param end_index the index of the last song (excluding) - */ -enum playlist_result -playlist_load_into_queue(const char *uri, struct playlist_provider *source, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure); - -/** - * Opens a playlist with a playlist plugin and append to the specified - * play queue. - */ -enum playlist_result -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - struct playlist *dest, struct player_control *pc, - bool secure); - -#endif - diff --git a/src/playlist_save.c b/src/playlist_save.c deleted file mode 100644 index 194bff057..000000000 --- a/src/playlist_save.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_save.h" -#include "playlist.h" -#include "stored_playlist.h" -#include "queue.h" -#include "song.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "database.h" -#include "idle.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <string.h> - -void -playlist_print_song(FILE *file, const struct song *song) -{ - if (playlist_saveAbsolutePaths && song_in_database(song)) { - char *path = map_song_fs(song); - if (path != NULL) { - fprintf(file, "%s\n", path); - g_free(path); - } - } else { - char *uri = song_get_uri(song), *uri_fs; - - uri_fs = utf8_to_fs_charset(uri); - g_free(uri); - - fprintf(file, "%s\n", uri_fs); - g_free(uri_fs); - } -} - -void -playlist_print_uri(FILE *file, const char *uri) -{ - char *s; - - if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - !g_path_is_absolute(uri)) - s = map_uri_fs(uri); - else - s = utf8_to_fs_charset(uri); - - if (s != NULL) { - fprintf(file, "%s\n", s); - g_free(s); - } -} - -enum playlist_result -spl_save_queue(const char *name_utf8, const struct queue *queue) -{ - char *path_fs; - FILE *file; - - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; - - if (!spl_valid_name(name_utf8)) - return PLAYLIST_RESULT_BAD_NAME; - - path_fs = map_spl_utf8_to_fs(name_utf8); - if (path_fs == NULL) - return PLAYLIST_RESULT_BAD_NAME; - - if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) { - g_free(path_fs); - return PLAYLIST_RESULT_LIST_EXISTS; - } - - file = fopen(path_fs, "w"); - g_free(path_fs); - - if (file == NULL) - return PLAYLIST_RESULT_ERRNO; - - for (unsigned i = 0; i < queue_length(queue); i++) - playlist_print_song(file, queue_get(queue, i)); - - fclose(file); - - idle_add(IDLE_STORED_PLAYLIST); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -spl_save_playlist(const char *name_utf8, const struct playlist *playlist) -{ - return spl_save_queue(name_utf8, &playlist->queue); -} - -bool -playlist_load_spl(struct playlist *playlist, struct player_control *pc, - const char *name_utf8, - unsigned start_index, unsigned end_index, - GError **error_r) -{ - GPtrArray *list; - - list = spl_load(name_utf8, error_r); - if (list == NULL) - return false; - - if (list->len < end_index) - end_index = list->len; - - for (unsigned i = start_index; i < end_index; ++i) { - const char *temp = g_ptr_array_index(list, i); - - if (memcmp(temp, "file:///", 8) == 0) { - const char *path = temp + 7; - - if (playlist_append_file(playlist, pc, path, NULL) != PLAYLIST_RESULT_SUCCESS) - g_warning("can't add file \"%s\"", path); - continue; - } - - if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { - /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(temp); - char *p = temp2; - while (*p) { - if (*p == '\\') - *p = '/'; - p++; - } - if ((playlist_append_uri(playlist, pc, temp2, - NULL)) != PLAYLIST_RESULT_SUCCESS) { - g_warning("can't add file \"%s\"", temp2); - } - g_free(temp2); - } - } - - spl_free(list); - return true; -} diff --git a/src/playlist_save.h b/src/playlist_save.h deleted file mode 100644 index a6c31a9a6..000000000 --- a/src/playlist_save.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SAVE_H -#define MPD_PLAYLIST_SAVE_H - -#include "playlist_error.h" - -#include <stdbool.h> -#include <stdio.h> - -struct song; -struct queue; -struct playlist; -struct player_control; - -void -playlist_print_song(FILE *fp, const struct song *song); - -void -playlist_print_uri(FILE *fp, const char *uri); - -/** - * Saves a queue object into a stored playlist file. - */ -enum playlist_result -spl_save_queue(const char *name_utf8, const struct queue *queue); - -/** - * Saves a playlist object into a stored playlist file. - */ -enum playlist_result -spl_save_playlist(const char *name_utf8, const struct playlist *playlist); - -/** - * Loads a stored playlist file, and append all songs to the global - * playlist. - */ -bool -playlist_load_spl(struct playlist *playlist, struct player_control *pc, - const char *name_utf8, - unsigned start_index, unsigned end_index, - GError **error_r); - -#endif diff --git a/src/playlist_song.c b/src/playlist_song.c deleted file mode 100644 index a3d9ab4d9..000000000 --- a/src/playlist_song.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_song.h" -#include "database.h" -#include "mapper.h" -#include "song.h" -#include "uri.h" -#include "path.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; - - char *path_utf8 = fs_charset_to_utf8(path_fs); - if (path_utf8 != NULL) - g_free(path_fs); - else - path_utf8 = path_fs; - - tmp = song_file_new(path_utf8, NULL); - g_free(path_utf8); - - merge_song_metadata(tmp, dest, src); - } else { - tmp = song_file_new(dest->uri, NULL); - merge_song_metadata(tmp, dest, src); - } - - 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; - - if (!song_in_database(dest)) - song_free(dest); - - return tmp; -} - -static struct song * -playlist_check_load_song(const struct song *song, const char *uri, bool secure) -{ - struct song *dest; - - if (uri_has_scheme(uri)) { - dest = song_remote_new(uri); - } else if (g_path_is_absolute(uri) && secure) { - dest = song_file_load(uri, NULL); - if (dest == NULL) - return NULL; - } else { - dest = db_get_song(uri); - if (dest == NULL) - /* not found in database */ - return NULL; - } - - return apply_song_metadata(dest, song); -} - -struct song * -playlist_check_translate_song(struct song *song, const char *base_uri, - bool secure) -{ - if (song_in_database(song)) - /* already ok */ - return song; - - const 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 (base_uri != NULL && strcmp(base_uri, ".") == 0) - /* g_path_get_dirname() returns "." when there is no - directory name in the given path; clear that now, - because it would break the database lookup - functions */ - base_uri = NULL; - - if (g_path_is_absolute(uri)) { - /* XXX fs_charset vs utf8? */ - const char *suffix = map_to_relative_path(uri); - assert(suffix != NULL); - - if (suffix != uri) - uri = suffix; - else if (!secure) { - /* local files must be relative to the music - directory when "secure" is enabled */ - song_free(song); - return NULL; - } - - base_uri = NULL; - } - - char *allocated = NULL; - if (base_uri != NULL) - uri = allocated = g_build_filename(base_uri, uri, NULL); - - struct song *dest = playlist_check_load_song(song, uri, secure); - song_free(song); - g_free(allocated); - return dest; -} diff --git a/src/playlist_song.h b/src/playlist_song.h deleted file mode 100644 index ea8786912..000000000 --- a/src/playlist_song.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SONG_H -#define MPD_PLAYLIST_SONG_H - -#include <stdbool.h> - -/** - * Verifies the song, returns NULL if it is unsafe. Translate the - * song to a new song object within the database, if it is a local - * file. The old song object is freed. - * - * @param secure if true, then local files are only allowed if they - * are relative to base_uri - */ -struct song * -playlist_check_translate_song(struct song *song, const char *base_uri, - bool secure); - -#endif diff --git a/src/playlist_state.c b/src/playlist_state.c deleted file mode 100644 index 4aa2c2c92..000000000 --- a/src/playlist_state.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#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 "conf.h" - -#include <string.h> -#include <stdlib.h> - -#define PLAYLIST_STATE_FILE_STATE "state: " -#define PLAYLIST_STATE_FILE_RANDOM "random: " -#define PLAYLIST_STATE_FILE_REPEAT "repeat: " -#define PLAYLIST_STATE_FILE_SINGLE "single: " -#define PLAYLIST_STATE_FILE_CONSUME "consume: " -#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" - -#define PLAYLIST_STATE_FILE_STATE_PLAY "play" -#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" -#define PLAYLIST_STATE_FILE_STATE_STOP "stop" - -#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX - -void -playlist_state_save(FILE *fp, const struct playlist *playlist, - struct player_control *pc) -{ - struct player_status player_status; - - pc_get_status(pc, &player_status); - - fputs(PLAYLIST_STATE_FILE_STATE, fp); - - if (playlist->playing) { - switch (player_status.state) { - case PLAYER_STATE_PAUSE: - fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); - break; - default: - fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); - } - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - queue_order_to_position(&playlist->queue, - playlist->current)); - 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, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", - (int)(pc_get_cross_fade(pc))); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", - pc_get_mixramp_db(pc)); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc_get_mixramp_delay(pc)); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); - queue_save(fp, &playlist->queue); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); -} - -static void -playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) -{ - const char *line = read_text_line(fp, buffer); - if (line == NULL) { - g_warning("No playlist in state file"); - return; - } - - while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - queue_load_song(fp, buffer, line, &playlist->queue); - - line = read_text_line(fp, buffer); - if (line == NULL) { - g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END - "' not found in state file"); - break; - } - } - - queue_increment_version(&playlist->queue); -} - -bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist, struct player_control *pc) -{ - int current = -1; - int seek_time = 0; - enum player_state state = PLAYER_STATE_STOP; - bool random_mode = false; - - if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) - return false; - - 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(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), - "1") == 0) { - playlist_set_repeat(playlist, pc, true); - } else - playlist_set_repeat(playlist, pc, false); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), - "1") == 0) { - playlist_set_single(playlist, pc, true); - } else - playlist_set_single(playlist, pc, false); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { - if (strcmp - (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), - "1") == 0) { - playlist_set_consume(playlist, true); - } else - playlist_set_consume(playlist, false); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { - pc_set_cross_fade(pc, - atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { - pc_set_mixramp_db(pc, - atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { - pc_set_mixramp_delay(pc, - atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { - random_mode = - strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), - "1") == 0; - } 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(line, - PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(fp, buffer, playlist); - } - } - - playlist_set_random(playlist, pc, random_mode); - - if (!queue_is_empty(&playlist->queue)) { - if (!queue_valid_position(&playlist->queue, current)) - current = 0; - - if (state == PLAYER_STATE_PLAY && - config_get_bool("restore_paused", false)) - /* the user doesn't want MPD to auto-start - playback after startup; fall back to - "pause" */ - state = PLAYER_STATE_PAUSE; - - /* enable all devices for the first time; this must be - called here, after the audio output states were - restored, before playback begins */ - if (state != PLAYER_STATE_STOP) - pc_update_audio(pc); - - if (state == PLAYER_STATE_STOP /* && config_option */) - playlist->current = current; - else if (seek_time == 0) - playlist_play(playlist, pc, current); - else - playlist_seek_song(playlist, pc, current, seek_time); - - if (state == PLAYER_STATE_PAUSE) - pc_pause(pc); - } - - return true; -} - -unsigned -playlist_state_get_hash(const struct playlist *playlist, - struct player_control *pc) -{ - struct player_status player_status; - - pc_get_status(pc, &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(pc) << 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 deleted file mode 100644 index f67d01d2c..000000000 --- a/src/playlist_state.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#ifndef PLAYLIST_STATE_H -#define PLAYLIST_STATE_H - -#include <glib.h> -#include <stdbool.h> -#include <stdio.h> - -struct playlist; -struct player_control; - -void -playlist_state_save(FILE *fp, const struct playlist *playlist, - struct player_control *pc); - -bool -playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist, struct player_control *pc); - -/** - * 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, - struct player_control *pc); - -#endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c deleted file mode 100644 index 74c7bf089..000000000 --- a/src/playlist_vector.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist_vector.h" -#include "db_lock.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 list_head *pv) -{ - assert(pv != NULL); - - struct playlist_metadata *pm, *n; - playlist_vector_for_each_safe(pm, n, pv) - playlist_metadata_free(pm); -} - -struct playlist_metadata * -playlist_vector_find(struct list_head *pv, const char *name) -{ - assert(holding_db_lock()); - assert(pv != NULL); - assert(name != NULL); - - struct playlist_metadata *pm; - playlist_vector_for_each(pm, pv) - if (strcmp(pm->name, name) == 0) - return pm; - - return NULL; -} - -void -playlist_vector_add(struct list_head *pv, - const char *name, time_t mtime) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_metadata_new(name, mtime); - list_add_tail(&pm->siblings, pv); -} - -bool -playlist_vector_update_or_add(struct list_head *pv, - const char *name, time_t mtime) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_vector_find(pv, name); - if (pm != NULL) { - if (mtime == pm->mtime) - return false; - - pm->mtime = mtime; - } else - playlist_vector_add(pv, name, mtime); - - return true; -} - -bool -playlist_vector_remove(struct list_head *pv, const char *name) -{ - assert(holding_db_lock()); - - struct playlist_metadata *pm = playlist_vector_find(pv, name); - if (pm == NULL) - return false; - - list_del(&pm->siblings); - playlist_metadata_free(pm); - return true; -} diff --git a/src/playlist_vector.h b/src/playlist_vector.h deleted file mode 100644 index 0af6df8b4..000000000 --- a/src/playlist_vector.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_VECTOR_H -#define MPD_PLAYLIST_VECTOR_H - -#include "util/list.h" - -#include <stdbool.h> -#include <stddef.h> -#include <sys/time.h> - -#define playlist_vector_for_each(pos, head) \ - list_for_each_entry(pos, head, siblings) - -#define playlist_vector_for_each_safe(pos, n, head) \ - list_for_each_entry_safe(pos, n, head, siblings) - -/** - * A directory entry pointing to a playlist file. - */ -struct playlist_metadata { - struct list_head siblings; - - /** - * The UTF-8 encoded name of the playlist file. - */ - char *name; - - time_t mtime; -}; - -void -playlist_vector_deinit(struct list_head *pv); - -/** - * Caller must lock the #db_mutex. - */ -struct playlist_metadata * -playlist_vector_find(struct list_head *pv, const char *name); - -/** - * Caller must lock the #db_mutex. - */ -void -playlist_vector_add(struct list_head *pv, - const char *name, time_t mtime); - -/** - * Caller must lock the #db_mutex. - * - * @return true if the vector or one of its items was modified - */ -bool -playlist_vector_update_or_add(struct list_head *pv, - const char *name, time_t mtime); - -/** - * Caller must lock the #db_mutex. - */ -bool -playlist_vector_remove(struct list_head *pv, const char *name); - -#endif /* SONGVEC_H */ diff --git a/src/protocol/Ack.cxx b/src/protocol/Ack.cxx new file mode 100644 index 000000000..3c8185afd --- /dev/null +++ b/src/protocol/Ack.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Ack.hxx" +#include "util/Domain.hxx" + +const Domain ack_domain("ack"); diff --git a/src/protocol/Ack.hxx b/src/protocol/Ack.hxx new file mode 100644 index 000000000..a7f8e5e53 --- /dev/null +++ b/src/protocol/Ack.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ACK_H +#define MPD_ACK_H + +class Domain; + +enum ack { + ACK_ERROR_NOT_LIST = 1, + ACK_ERROR_ARG = 2, + ACK_ERROR_PASSWORD = 3, + ACK_ERROR_PERMISSION = 4, + ACK_ERROR_UNKNOWN = 5, + + ACK_ERROR_NO_EXIST = 50, + ACK_ERROR_PLAYLIST_MAX = 51, + ACK_ERROR_SYSTEM = 52, + ACK_ERROR_PLAYLIST_LOAD = 53, + ACK_ERROR_UPDATE_ALREADY = 54, + ACK_ERROR_PLAYER_SYNC = 55, + ACK_ERROR_EXIST = 56, +}; + +extern const Domain ack_domain; + +#endif diff --git a/src/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx new file mode 100644 index 000000000..6bd53a358 --- /dev/null +++ b/src/protocol/ArgParser.cxx @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ArgParser.hxx" +#include "Result.hxx" + +#include <glib.h> +#include <stdlib.h> + +bool +check_uint32(Client *client, uint32_t *dst, const char *s) +{ + char *test; + + *dst = strtoul(s, &test, 10); + if (test == s || *test != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + return true; +} + +bool +check_int(Client *client, int *value_r, const char *s) +{ + char *test; + long value; + + value = strtol(s, &test, 10); + if (test == s || *test != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXINT + if (value < G_MININT || value > G_MAXINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + + *value_r = (int)value; + return true; +} + +bool +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, + const char *s) +{ + char *test, *test2; + long value; + + value = strtol(s, &test, 10); + if (test == s || (*test != '\0' && *test != ':')) { + command_error(client, ACK_ERROR_ARG, + "Integer or range expected: %s", s); + return false; + } + + if (value == -1 && *test == 0) { + /* compatibility with older MPD versions: specifying + "-1" makes MPD display the whole list */ + *value_r1 = 0; + *value_r2 = G_MAXUINT; + return true; + } + + if (value < 0) { + command_error(client, ACK_ERROR_ARG, + "Number is negative: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + + *value_r1 = (unsigned)value; + + if (*test == ':') { + value = strtol(++test, &test2, 10); + if (*test2 != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer or range expected: %s", s); + return false; + } + + if (test == test2) + value = G_MAXUINT; + + if (value < 0) { + command_error(client, ACK_ERROR_ARG, + "Number is negative: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + *value_r2 = (unsigned)value; + } else { + *value_r2 = (unsigned)value + 1; + } + + return true; +} + +bool +check_unsigned(Client *client, unsigned *value_r, const char *s) +{ + unsigned long value; + char *endptr; + + value = strtoul(s, &endptr, 10); + if (endptr == s || *endptr != 0) { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } + + *value_r = (unsigned)value; + return true; +} + +bool +check_bool(Client *client, bool *value_r, const char *s) +{ + long value; + char *endptr; + + value = strtol(s, &endptr, 10); + if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) { + command_error(client, ACK_ERROR_ARG, + "Boolean (0/1) expected: %s", s); + return false; + } + + *value_r = !!value; + return true; +} + +bool +check_float(Client *client, float *value_r, const char *s) +{ + float value; + char *endptr; + + value = strtof(s, &endptr); + if (endptr == s || *endptr != 0) { + command_error(client, ACK_ERROR_ARG, + "Float expected: %s", s); + return false; + } + + *value_r = value; + return true; +} diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx new file mode 100644 index 000000000..f69248d2d --- /dev/null +++ b/src/protocol/ArgParser.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROTOCOL_ARGPARSER_HXX +#define MPD_PROTOCOL_ARGPARSER_HXX + +#include "check.h" + +#include <stdint.h> + +class Client; + +bool +check_uint32(Client *client, uint32_t *dst, const char *s); + +bool +check_int(Client *client, int *value_r, const char *s); + +bool +check_range(Client *client, unsigned *value_r1, unsigned *value_r2, + const char *s); + +bool +check_unsigned(Client *client, unsigned *value_r, const char *s); + +bool +check_bool(Client *client, bool *value_r, const char *s); + +bool +check_float(Client *client, float *value_r, const char *s); + +#endif diff --git a/src/protocol/Result.cxx b/src/protocol/Result.cxx new file mode 100644 index 000000000..e10a731cc --- /dev/null +++ b/src/protocol/Result.cxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Result.hxx" +#include "Client.hxx" + +#include <assert.h> + +const char *current_command; +int command_list_num; + +void +command_success(Client *client) +{ + client_puts(client, "OK\n"); +} + +void +command_error_v(Client *client, enum ack error, + const char *fmt, va_list args) +{ + assert(client != NULL); + assert(current_command != NULL); + + client_printf(client, "ACK [%i@%i] {%s} ", + (int)error, command_list_num, current_command); + client_vprintf(client, fmt, args); + client_puts(client, "\n"); + + current_command = NULL; +} + +void +command_error(Client *client, enum ack error, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + command_error_v(client, error, fmt, args); + va_end(args); +} diff --git a/src/protocol/Result.hxx b/src/protocol/Result.hxx new file mode 100644 index 000000000..0f953c62b --- /dev/null +++ b/src/protocol/Result.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROTOCOL_RESULT_HXX +#define MPD_PROTOCOL_RESULT_HXX + +#include "check.h" +#include "gcc.h" +#include "Ack.hxx" + +#include <stdarg.h> + +class Client; + +extern const char *current_command; +extern int command_list_num; + +void +command_success(Client *client); + +void +command_error_v(Client *client, enum ack error, + const char *fmt, va_list args); + +gcc_fprintf_ +void +command_error(Client *client, enum ack error, const char *fmt, ...); + +#endif diff --git a/src/protocol/argparser.c b/src/protocol/argparser.c deleted file mode 100644 index d20437cb7..000000000 --- a/src/protocol/argparser.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "argparser.h" -#include "result.h" - -#include <glib.h> -#include <stdlib.h> - -bool -check_uint32(struct client *client, uint32_t *dst, const char *s) -{ - char *test; - - *dst = strtoul(s, &test, 10); - if (test == s || *test != '\0') { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - return true; -} - -bool -check_int(struct client *client, int *value_r, const char *s) -{ - char *test; - long value; - - value = strtol(s, &test, 10); - if (test == s || *test != '\0') { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXINT - if (value < G_MININT || value > G_MAXINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r = (int)value; - return true; -} - -bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, - const char *s) -{ - char *test, *test2; - long value; - - value = strtol(s, &test, 10); - if (test == s || (*test != '\0' && *test != ':')) { - command_error(client, ACK_ERROR_ARG, - "Integer or range expected: %s", s); - return false; - } - - if (value == -1 && *test == 0) { - /* compatibility with older MPD versions: specifying - "-1" makes MPD display the whole list */ - *value_r1 = 0; - *value_r2 = G_MAXUINT; - return true; - } - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r1 = (unsigned)value; - - if (*test == ':') { - value = strtol(++test, &test2, 10); - if (*test2 != '\0') { - command_error(client, ACK_ERROR_ARG, - "Integer or range expected: %s", s); - return false; - } - - if (test == test2) - value = G_MAXUINT; - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - *value_r2 = (unsigned)value; - } else { - *value_r2 = (unsigned)value + 1; - } - - return true; -} - -bool -check_unsigned(struct client *client, unsigned *value_r, const char *s) -{ - unsigned long value; - char *endptr; - - value = strtoul(s, &endptr, 10); - if (endptr == s || *endptr != 0) { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } - - *value_r = (unsigned)value; - return true; -} - -bool -check_bool(struct client *client, bool *value_r, const char *s) -{ - long value; - char *endptr; - - value = strtol(s, &endptr, 10); - if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) { - command_error(client, ACK_ERROR_ARG, - "Boolean (0/1) expected: %s", s); - return false; - } - - *value_r = !!value; - return true; -} - -bool -check_float(struct client *client, float *value_r, const char *s) -{ - float value; - char *endptr; - - value = strtof(s, &endptr); - if (endptr == s || *endptr != 0) { - command_error(client, ACK_ERROR_ARG, - "Float expected: %s", s); - return false; - } - - *value_r = value; - return true; -} diff --git a/src/protocol/argparser.h b/src/protocol/argparser.h deleted file mode 100644 index e88aea478..000000000 --- a/src/protocol/argparser.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PROTOCOL_ARGPARSER_H -#define MPD_PROTOCOL_ARGPARSER_H - -#include "check.h" - -#include <stdbool.h> -#include <stdint.h> - -struct client; - -bool -check_uint32(struct client *client, uint32_t *dst, const char *s); - -bool -check_int(struct client *client, int *value_r, const char *s); - -bool -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, - const char *s); - -bool -check_unsigned(struct client *client, unsigned *value_r, const char *s); - -bool -check_bool(struct client *client, bool *value_r, const char *s); - -bool -check_float(struct client *client, float *value_r, const char *s); - -#endif diff --git a/src/protocol/result.c b/src/protocol/result.c deleted file mode 100644 index 30cd0a266..000000000 --- a/src/protocol/result.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "result.h" -#include "client.h" - -#include <assert.h> - -const char *current_command; -int command_list_num; - -void -command_success(struct client *client) -{ - client_puts(client, "OK\n"); -} - -void -command_error_v(struct client *client, enum ack error, - const char *fmt, va_list args) -{ - assert(client != NULL); - assert(current_command != NULL); - - client_printf(client, "ACK [%i@%i] {%s} ", - (int)error, command_list_num, current_command); - client_vprintf(client, fmt, args); - client_puts(client, "\n"); - - current_command = NULL; -} - -void -command_error(struct client *client, enum ack error, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - command_error_v(client, error, fmt, args); - va_end(args); -} diff --git a/src/protocol/result.h b/src/protocol/result.h deleted file mode 100644 index 8b9e44bfd..000000000 --- a/src/protocol/result.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PROTOCOL_RESULT_H -#define MPD_PROTOCOL_RESULT_H - -#include "check.h" -#include "ack.h" - -#include <glib.h> - -struct client; - -extern const char *current_command; -extern int command_list_num; - -void -command_success(struct client *client); - -void -command_error_v(struct client *client, enum ack error, - const char *fmt, va_list args); - -G_GNUC_PRINTF(3, 4) -void -command_error(struct client *client, enum ack error, const char *fmt, ...); - -#endif diff --git a/src/queue.c b/src/queue.c deleted file mode 100644 index 4fe564a35..000000000 --- a/src/queue.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue.h" -#include "song.h" - -#include <stdlib.h> - -/** - * Generate a non-existing id number. - */ -static unsigned -queue_generate_id(const struct queue *queue) -{ - static unsigned cur = (unsigned)-1; - - do { - cur++; - - if (cur >= queue->max_length * QUEUE_HASH_MULT) - cur = 0; - } while (queue->id_to_position[cur] != -1); - - return cur; -} - -int -queue_next_order(const struct queue *queue, unsigned order) -{ - assert(order < 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 */ - return 0; - else - /* end of queue */ - return -1; -} - -void -queue_increment_version(struct queue *queue) -{ - static unsigned long max = ((uint32_t) 1 << 31) - 1; - - queue->version++; - - if (queue->version >= max) { - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = 0; - - queue->version = 1; - } -} - -void -queue_modify(struct queue *queue, unsigned order) -{ - unsigned position; - - assert(order < queue->length); - - position = queue->order[order]; - queue->items[position].version = queue->version; - - queue_increment_version(queue); -} - -void -queue_modify_all(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = queue->version; - - queue_increment_version(queue); -} - -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority) -{ - unsigned id = queue_generate_id(queue); - - assert(!queue_is_full(queue)); - - queue->items[queue->length] = (struct queue_item){ - .song = song, - .id = id, - .version = queue->version, - .priority = priority, - }; - - queue->order[queue->length] = queue->length; - queue->id_to_position[id] = queue->length; - - ++queue->length; - - return id; -} - -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2) -{ - struct queue_item tmp; - unsigned id1 = queue->items[position1].id; - unsigned id2 = queue->items[position2].id; - - tmp = queue->items[position1]; - queue->items[position1] = queue->items[position2]; - queue->items[position2] = tmp; - - queue->items[position1].version = queue->version; - queue->items[position2].version = queue->version; - - queue->id_to_position[id1] = position2; - queue->id_to_position[id2] = position1; -} - -static void -queue_move_song_to(struct queue *queue, unsigned from, unsigned to) -{ - unsigned from_id = queue->items[from].id; - - queue->items[to] = queue->items[from]; - queue->items[to].version = queue->version; - queue->id_to_position[from_id] = to; -} - -void -queue_move(struct queue *queue, unsigned from, unsigned to) -{ - struct queue_item item = queue->items[from]; - - /* move songs to one less in from->to */ - - for (unsigned i = from; i < to; i++) - queue_move_song_to(queue, i + 1, i); - - /* move songs to one more in to->from */ - - for (unsigned i = from; i > to; i--) - queue_move_song_to(queue, i - 1, i); - - /* put song at _to_ */ - - queue->id_to_position[item.id] = to; - queue->items[to] = item; - queue->items[to].version = queue->version; - - /* now deal with order */ - - if (queue->random) { - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] > from && queue->order[i] <= to) - queue->order[i]--; - else if (queue->order[i] < from && - queue->order[i] >= to) - queue->order[i]++; - else if (from == queue->order[i]) - queue->order[i] = to; - } - } -} - -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) -{ - struct queue_item items[end - start]; - // Copy the original block [start,end-1] - for (unsigned i = start; i < end; i++) - items[i - start] = queue->items[i]; - - // If to > start, we need to move to-start items to start, starting from end - for (unsigned i = end; i < end + to - start; i++) - queue_move_song_to(queue, i, start + i - end); - - // If to < start, we need to move start-to items to newend (= end + to - start), starting from to - // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 - // We have to iterate in this order to avoid writing over something we haven't yet moved - for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--) - queue_move_song_to(queue, i, i + end - start); - - // Copy the original block back in, starting at to. - for (unsigned i = start; i< end; i++) - { - 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; - } - - if (queue->random) { - // Update the positions in the queue. - // Note that the ranges for these cases are the same as the ranges of - // the loops above. - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] >= end && queue->order[i] < to + end - start) - queue->order[i] -= end - start; - else if (queue->order[i] < start && - queue->order[i] >= to) - queue->order[i] += end - start; - else if (start <= queue->order[i] && queue->order[i] < end) - queue->order[i] += to - start; - } - } -} - -/** - * Moves a song to a new position in the "order" list. - */ -static void -queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) -{ - assert(queue != NULL); - assert(from_order < queue->length); - assert(to_order <= queue->length); - - const unsigned from_position = - queue_order_to_position(queue, from_order); - - if (from_order < to_order) { - for (unsigned i = from_order; i < to_order; ++i) - queue->order[i] = queue->order[i + 1]; - } else { - for (unsigned i = from_order; i > to_order; --i) - queue->order[i] = queue->order[i - 1]; - } - - queue->order[to_order] = from_position; -} - -void -queue_delete(struct queue *queue, unsigned position) -{ - struct song *song; - unsigned id, order; - - assert(position < queue->length); - - song = queue_get(queue, position); - if (!song_in_database(song)) - song_free(song); - - id = queue_position_to_id(queue, position); - order = queue_position_to_order(queue, position); - - --queue->length; - - /* release the song id */ - - queue->id_to_position[id] = -1; - - /* delete song from songs array */ - - for (unsigned i = position; i < queue->length; i++) - queue_move_song_to(queue, i + 1, i); - - /* delete the entry from the order array */ - - for (unsigned i = order; i < queue->length; i++) - queue->order[i] = queue->order[i + 1]; - - /* readjust values in the order array */ - - for (unsigned i = 0; i < queue->length; i++) - if (queue->order[i] > position) - --queue->order[i]; -} - -void -queue_clear(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) { - struct queue_item *item = &queue->items[i]; - - if (!song_in_database(item->song)) - song_free(item->song); - - queue->id_to_position[item->id] = -1; - } - - queue->length = 0; -} - -void -queue_init(struct queue *queue, unsigned max_length) -{ - queue->max_length = max_length; - queue->length = 0; - queue->version = 1; - queue->repeat = false; - queue->random = false; - queue->single = false; - queue->consume = false; - - queue->items = g_new(struct queue_item, max_length); - queue->order = g_malloc(sizeof(queue->order[0]) * - max_length); - 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->id_to_position[i] = -1; - - queue->rand = g_rand_new(); -} - -void -queue_finish(struct queue *queue) -{ - queue_clear(queue); - - g_free(queue->items); - g_free(queue->order); - g_free(queue->id_to_position); - - g_rand_free(queue->rand); -} - -static const struct queue_item * -queue_get_order_item_const(const struct queue *queue, unsigned order) -{ - assert(queue != NULL); - assert(order < queue->length); - - return &queue->items[queue->order[order]]; -} - -static uint8_t -queue_get_order_priority(const struct queue *queue, unsigned order) -{ - return queue_get_order_item_const(queue, order)->priority; -} - -static gint -queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, - gpointer user_data) -{ - const struct queue *queue = user_data; - const unsigned *const ap = av; - const unsigned *const bp = bv; - assert(ap >= queue->order && ap < queue->order + queue->length); - assert(bp >= queue->order && bp < queue->order + queue->length); - uint8_t a = queue->items[*ap].priority; - uint8_t b = queue->items[*bp].priority; - - if (G_LIKELY(a == b)) - return 0; - else if (a > b) - return -1; - else - return 1; -} - -static void -queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - g_qsort_with_data(&queue->order[start], end - start, - sizeof(queue->order[0]), - queue_item_compare_order_priority, - queue); -} - -/** - * Shuffle the order of items in the specified range, ignoring their - * priorities. - */ -static void -queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; ++i) - queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, end)); -} - -/** - * Sort the "order" of items by priority, and then shuffle each - * priority group. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - if (start == end) - return; - - /* first group the range by priority */ - queue_sort_order_by_priority(queue, start, end); - - /* now shuffle each priority group */ - unsigned group_start = start; - uint8_t group_priority = queue_get_order_priority(queue, start); - - for (unsigned i = start + 1; i < end; ++i) { - uint8_t priority = queue_get_order_priority(queue, i); - assert(priority <= group_priority); - - if (priority != group_priority) { - /* start of a new group - shuffle the one that - has just ended */ - queue_shuffle_order_range(queue, group_start, i); - group_start = i; - group_priority = priority; - } - } - - /* shuffle the last group */ - queue_shuffle_order_range(queue, group_start, end); -} - -void -queue_shuffle_order(struct queue *queue) -{ - queue_shuffle_order_range_with_priority(queue, 0, queue->length); -} - -static void -queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, start, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, end - 1, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; i++) { - unsigned ri = g_rand_int_range(queue->rand, i, end); - queue_swap(queue, i, ri); - } -} - -/** - * Find the first item that has this specified priority or higher. - */ -G_GNUC_PURE -static unsigned -queue_find_priority_order(const struct queue *queue, unsigned start_order, - uint8_t priority, unsigned exclude_order) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority <= priority && order != exclude_order) - return order; - } - - return queue->length; -} - -G_GNUC_PURE -static unsigned -queue_count_same_priority(const struct queue *queue, unsigned start_order, - uint8_t priority) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority != priority) - return order - start_order; - } - - return queue->length - start_order; -} - -bool -queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, - int after_order) -{ - assert(queue != NULL); - assert(position < queue->length); - - struct queue_item *item = &queue->items[position]; - uint8_t old_priority = item->priority; - if (old_priority == priority) - return false; - - item->version = queue->version; - item->priority = priority; - - if (!queue->random) - /* don't reorder if not in random mode */ - return true; - - unsigned order = queue_position_to_order(queue, position); - if (after_order >= 0) { - if (order == (unsigned)after_order) - /* don't reorder the current song */ - return true; - - if (order < (unsigned)after_order) { - /* the specified song has been played already - - enqueue it only if its priority has just - become bigger than the current one's */ - - const unsigned after_position = - queue_order_to_position(queue, after_order); - const struct queue_item *after_item = - &queue->items[after_position]; - if (old_priority > after_item->priority || - priority <= after_item->priority) - /* priority hasn't become bigger */ - return true; - } - } - - /* move the item to the beginning of the priority group (or - create a new priority group) */ - - const unsigned before_order = - queue_find_priority_order(queue, after_order + 1, priority, - order); - const unsigned new_order = before_order > order - ? before_order - 1 - : before_order; - queue_move_order(queue, order, new_order); - - /* shuffle the song within that priority group */ - - const unsigned priority_count = - queue_count_same_priority(queue, new_order, priority); - assert(priority_count >= 1); - queue_shuffle_order_first(queue, new_order, - new_order + priority_count); - - return true; -} - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order) -{ - assert(queue != NULL); - assert(start_position <= end_position); - assert(end_position <= queue->length); - - bool modified = false; - int after_position = after_order >= 0 - ? (int)queue_order_to_position(queue, after_order) - : -1; - for (unsigned i = start_position; i < end_position; ++i) { - after_order = after_position >= 0 - ? (int)queue_position_to_order(queue, after_position) - : -1; - - modified |= queue_set_priority(queue, i, priority, - after_order); - } - - return modified; -} diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index e4bfcdffa..000000000 --- a/src/queue.h +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef QUEUE_H -#define QUEUE_H - -#include <glib.h> - -#include <assert.h> -#include <stdbool.h> -#include <stdint.h> - -enum { - /** - * reserve max_length * QUEUE_HASH_MULT elements in the id - * number space - */ - QUEUE_HASH_MULT = 4, -}; - -/** - * One element of the queue: basically a song plus some queue specific - * information attached. - */ -struct queue_item { - struct song *song; - - /** the unique id of this item in the queue */ - unsigned id; - - /** when was this item last changed? */ - uint32_t version; - - /** - * The priority of this item, between 0 and 255. High - * priority value means that this song gets played first in - * "random" mode. - */ - uint8_t priority; -}; - -/** - * A queue of songs. This is the backend of the playlist: it contains - * an ordered list of songs. - * - * Songs can be addressed in three possible ways: - * - * - the position in the queue - * - the unique id (which stays the same, regardless of moves) - * - the order number (which only differs from "position" in random mode) - */ -struct queue { - /** configured maximum length of the queue */ - unsigned max_length; - - /** number of songs in the queue */ - unsigned length; - - /** the current version number */ - uint32_t version; - - /** all songs in "position" order */ - struct queue_item *items; - - /** map order numbers to positions */ - unsigned *order; - - /** map song ids to positions */ - int *id_to_position; - - /** repeat playback when the end of the queue has been - reached? */ - bool repeat; - - /** play only current song. */ - bool single; - - /** remove each played files. */ - bool consume; - - /** play back songs in random order? */ - bool random; - - /** random number generator for shuffle and random mode */ - GRand *rand; -}; - -static inline unsigned -queue_length(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length; -} - -/** - * Determine if the queue is empty, i.e. there are no songs. - */ -static inline bool -queue_is_empty(const struct queue *queue) -{ - return queue->length == 0; -} - -/** - * Determine if the maximum number of songs has been reached. - */ -static inline bool -queue_is_full(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length >= queue->max_length; -} - -/** - * Is that a valid position number? - */ -static inline bool -queue_valid_position(const struct queue *queue, unsigned position) -{ - return position < queue->length; -} - -/** - * Is that a valid order number? - */ -static inline bool -queue_valid_order(const struct queue *queue, unsigned order) -{ - return order < queue->length; -} - -static inline int -queue_id_to_position(const struct queue *queue, unsigned id) -{ - if (id >= queue->max_length * QUEUE_HASH_MULT) - return -1; - - assert(queue->id_to_position[id] >= -1); - assert(queue->id_to_position[id] < (int)queue->length); - - return queue->id_to_position[id]; -} - -static inline int -queue_position_to_id(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].id; -} - -static inline unsigned -queue_order_to_position(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - return queue->order[order]; -} - -static inline unsigned -queue_position_to_order(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - for (unsigned i = 0;; ++i) { - assert(i < queue->length); - - if (queue->order[i] == position) - return i; - } -} - -G_GNUC_PURE -static inline uint8_t -queue_get_priority_at_position(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].priority; -} - -/** - * Returns the song at the specified position. - */ -static inline struct song * -queue_get(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].song; -} - -/** - * Returns the song at the specified order number. - */ -static inline struct song * -queue_get_order(const struct queue *queue, unsigned order) -{ - return queue_get(queue, queue_order_to_position(queue, order)); -} - -/** - * Is the song at the specified position newer than the specified - * version? - */ -static inline bool -queue_song_newer(const struct queue *queue, unsigned position, - uint32_t version) -{ - assert(position < queue->length); - - return version > queue->version || - queue->items[position].version >= version || - queue->items[position].version == 0; -} - -/** - * Initialize a queue object. - */ -void -queue_init(struct queue *queue, unsigned max_length); - -/** - * Deinitializes a queue object. It does not free the queue pointer - * itself. - */ -void -queue_finish(struct queue *queue); - -/** - * Returns the order number following the specified one. This takes - * end of queue and "repeat" mode into account. - * - * @return the next order number, or -1 to stop playback - */ -int -queue_next_order(const struct queue *queue, unsigned order); - -/** - * Increments the queue's version number. This handles integer - * overflow well. - */ -void -queue_increment_version(struct queue *queue); - -/** - * Marks the specified song as "modified" and increments the version - * number. - */ -void -queue_modify(struct queue *queue, unsigned order); - -/** - * Marks all songs as "modified" and increments the version number. - */ -void -queue_modify_all(struct queue *queue); - -/** - * Appends a song to the queue and returns its position. Prior to - * that, the caller must check if the queue is already full. - * - * If a song is not in the database (determined by - * song_in_database()), it is freed when removed from the queue. - * - * @param priority the priority of this new queue item - */ -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority); - -/** - * Swaps two songs, addressed by their position. - */ -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2); - -/** - * Swaps two songs, addressed by their order number. - */ -static inline void -queue_swap_order(struct queue *queue, unsigned order1, unsigned order2) -{ - unsigned tmp = queue->order[order1]; - queue->order[order1] = queue->order[order2]; - queue->order[order2] = tmp; -} - -/** - * Moves a song to a new position. - */ -void -queue_move(struct queue *queue, unsigned from, unsigned to); - -/** - * Moves a range of songs to a new position. - */ -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to); - -/** - * Removes a song from the playlist. - */ -void -queue_delete(struct queue *queue, unsigned position); - -/** - * Removes all songs from the playlist. - */ -void -queue_clear(struct queue *queue); - -/** - * Initializes the "order" array, and restores "normal" order. - */ -static inline void -queue_restore_order(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; ++i) - queue->order[i] = i; -} - -/** - * Shuffle the order of items in the specified range, taking their - * priorities into account. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end); - -/** - * Shuffles the virtual order of songs, but does not move them - * physically. This is used in random mode. - */ -void -queue_shuffle_order(struct queue *queue); - -/** - * Shuffles the virtual order of the last song in the specified - * (order) range. This is used in random mode after a song has been - * appended by queue_append(). - */ -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); - -/** - * Shuffles a (position) range in the queue. The songs are physically - * shuffled, not by using the "order" mapping. - */ -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); - -bool -queue_set_priority(struct queue *queue, unsigned position, - uint8_t priority, int after_order); - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order); - -#endif diff --git a/src/queue_print.c b/src/queue_print.c deleted file mode 100644 index d149e8b6f..000000000 --- a/src/queue_print.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 - * client. - * - * @param client the client which has requested information - * @param start the index of the first song (including) - * @param end the index of the last song (excluding) - */ -static void -queue_print_song_info(struct client *client, const struct queue *queue, - unsigned position) -{ - song_print_info(client, queue_get(queue, position)); - client_printf(client, "Pos: %u\nId: %u\n", - position, queue_position_to_id(queue, position)); - - uint8_t priority = queue_get_priority_at_position(queue, position); - if (priority != 0) - client_printf(client, "Prio: %u\n", priority); -} - -void -queue_print_info(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) - queue_print_song_info(client, queue, i); -} - -void -queue_print_uris(struct client *client, const struct queue *queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue_length(queue)); - - for (unsigned i = start; i < end; ++i) { - client_printf(client, "%i:", i); - song_print_uri(client, queue_get(queue, i)); - } -} - -void -queue_print_changes_info(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - if (queue_song_newer(queue, i, version)) - queue_print_song_info(client, queue, i); - } -} - -void -queue_print_changes_position(struct client *client, const struct queue *queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue_length(queue); i++) - if (queue_song_newer(queue, i, version)) - client_printf(client, "cpos: %i\nId: %i\n", - i, queue_position_to_id(queue, i)); -} - -void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - unsigned i; - struct locate_item_list *new_list = - locate_item_list_casefold(criteria); - - for (i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_search(song, new_list)) - queue_print_song_info(client, queue, i); - } - - locate_item_list_free(new_list); -} - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - - if (locate_song_match(song, criteria)) - queue_print_song_info(client, queue, i); - } -} diff --git a/src/queue_print.h b/src/queue_print.h deleted file mode 100644 index 371e20416..000000000 --- a/src/queue_print.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This library sends information about songs in the queue to the - * client. - */ - -#ifndef QUEUE_PRINT_H -#define QUEUE_PRINT_H - -#include <stdint.h> - -struct client; -struct queue; -struct locate_item_list; - -void -queue_print_info(struct client *client, const struct queue *queue, - unsigned start, unsigned end); - -void -queue_print_uris(struct client *client, const struct queue *queue, - unsigned start, unsigned end); - -void -queue_print_changes_info(struct client *client, const struct queue *queue, - uint32_t version); - -void -queue_print_changes_position(struct client *client, const struct queue *queue, - uint32_t version); - -void -queue_search(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -void -queue_find(struct client *client, const struct queue *queue, - const struct locate_item_list *criteria); - -#endif diff --git a/src/queue_save.c b/src/queue_save.c deleted file mode 100644 index 16852d3c1..000000000 --- a/src/queue_save.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "queue_save.h" -#include "queue.h" -#include "song.h" -#include "uri.h" -#include "database.h" -#include "song_save.h" -#include "text_file.h" - -#include <stdlib.h> - -#define PRIO_LABEL "Prio: " - -static void -queue_save_database_song(FILE *fp, int idx, const struct song *song) -{ - char *uri = song_get_uri(song); - - fprintf(fp, "%i:%s\n", idx, uri); - g_free(uri); -} - -static void -queue_save_full_song(FILE *fp, const struct song *song) -{ - song_save(fp, 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); -} - -void -queue_save(FILE *fp, const struct queue *queue) -{ - for (unsigned i = 0; i < queue_length(queue); i++) { - uint8_t prio = queue_get_priority_at_position(queue, i); - if (prio != 0) - fprintf(fp, PRIO_LABEL "%u\n", prio); - - queue_save_song(fp, i, queue_get(queue, i)); - } -} - -static struct song * -get_song(const char *uri) -{ - return uri_has_scheme(uri) - ? song_remote_new(uri) - : db_get_song(uri); -} - -void -queue_load_song(FILE *fp, GString *buffer, const char *line, - struct queue *queue) -{ - struct song *song; - - if (queue_is_full(queue)) - return; - - uint8_t priority = 0; - if (g_str_has_prefix(line, PRIO_LABEL)) { - priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10); - - line = read_text_line(fp, buffer); - if (line == NULL) - return; - } - - 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; - - 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; - } - - line = endptr + 1; - - song = get_song(line); - if (song == NULL) - return; - } - - queue_append(queue, song, priority); -} diff --git a/src/queue_save.h b/src/queue_save.h deleted file mode 100644 index 5526d615d..000000000 --- a/src/queue_save.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This library saves the queue into the state file, and also loads it - * back into memory. - */ - -#ifndef QUEUE_SAVE_H -#define QUEUE_SAVE_H - -#include <glib.h> -#include <stdio.h> - -struct queue; - -void -queue_save(FILE *fp, const struct queue *queue); - -/** - * Loads one song from the state file and appends it to the queue. - */ -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 deleted file mode 100644 index a882d76b0..000000000 --- a/src/refcount.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2011 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> -#include <stdbool.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_ape.c b/src/replay_gain_ape.c deleted file mode 100644 index 0b59e3c02..000000000 --- a/src/replay_gain_ape.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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/replay_gain_ape.h b/src/replay_gain_ape.h deleted file mode 100644 index 35760a0aa..000000000 --- a/src/replay_gain_ape.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_REPLAY_GAIN_APE_H -#define MPD_REPLAY_GAIN_APE_H - -#include "check.h" - -#include <stdbool.h> - -struct replay_gain_info; - -bool -replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info); - -#endif diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c deleted file mode 100644 index 2181387b7..000000000 --- a/src/replay_gain_config.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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_config.h b/src/replay_gain_config.h deleted file mode 100644 index 18747cef2..000000000 --- a/src/replay_gain_config.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_REPLAY_GAIN_CONFIG_H -#define MPD_REPLAY_GAIN_CONFIG_H - -#include "check.h" -#include "replay_gain_info.h" - -#include <stdbool.h> - -extern enum replay_gain_mode replay_gain_mode; -extern float replay_gain_preamp; -extern float replay_gain_missing_preamp; -extern bool replay_gain_limit; - -void replay_gain_global_init(void); - -/** - * 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 deleted file mode 100644 index 1f09e7a1a..000000000 --- a/src/replay_gain_info.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 9097c3e02..000000000 --- a/src/replay_gain_info.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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/resolver.c b/src/resolver.c deleted file mode 100644 index 243b7cd02..000000000 --- a/src/resolver.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "resolver.h" - -#ifndef G_OS_WIN32 -#include <sys/socket.h> -#include <netdb.h> -#else /* G_OS_WIN32 */ -#include <ws2tcpip.h> -#include <winsock.h> -#endif /* G_OS_WIN32 */ - -#include <string.h> - -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error) -{ -#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) - const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)sa; - struct sockaddr_in a4; -#endif - int ret; - char host[NI_MAXHOST], serv[NI_MAXSERV]; - -#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) - if (sa->sa_family == AF_INET6 && - IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) { - /* convert "::ffff:127.0.0.1" to "127.0.0.1" */ - - memset(&a4, 0, sizeof(a4)); - a4.sin_family = AF_INET; - memcpy(&a4.sin_addr, ((const char *)&a6->sin6_addr) + 12, - sizeof(a4.sin_addr)); - a4.sin_port = a6->sin6_port; - - sa = (const struct sockaddr *)&a4; - length = sizeof(a4); - } -#endif - - ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv), - NI_NUMERICHOST|NI_NUMERICSERV); - if (ret != 0) { - g_set_error(error, g_quark_from_static_string("netdb"), ret, - "%s", gai_strerror(ret)); - return NULL; - } - -#ifdef HAVE_UN - if (sa->sa_family == AF_UNIX) - /* "serv" contains corrupt information with unix - sockets */ - return g_strdup(host); -#endif - -#ifdef HAVE_IPV6 - if (strchr(host, ':') != NULL) - return g_strconcat("[", host, "]:", serv, NULL); -#endif - - return g_strconcat(host, ":", serv, NULL); -} - -struct addrinfo * -resolve_host_port(const char *host_port, unsigned default_port, - int flags, int socktype, - GError **error_r) -{ - char *p = g_strdup(host_port); - const char *host = p, *port = NULL; - - if (host_port[0] == '[') { - /* IPv6 needs enclosing square braces, to - differentiate between IP colons and the port - separator */ - - char *q = strchr(p + 1, ']'); - if (q != NULL && q[1] == ':' && q[2] != 0) { - *q = 0; - ++host; - port = q + 2; - } - } - - if (port == NULL) { - /* port is after the colon, but only if it's the only - colon (don't split IPv6 addresses) */ - - char *q = strchr(p, ':'); - if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) { - *q = 0; - port = q + 1; - } - } - - char buffer[32]; - if (port == NULL && default_port != 0) { - g_snprintf(buffer, sizeof(buffer), "%u", default_port); - port = buffer; - } - - if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0) - host = NULL; - - const struct addrinfo hints = { - .ai_flags = flags, - .ai_family = AF_UNSPEC, - .ai_socktype = socktype, - }; - - struct addrinfo *ai; - int ret = getaddrinfo(host, port, &hints, &ai); - g_free(p); - if (ret != 0) { - g_set_error(error_r, resolver_quark(), ret, - "Failed to look up '%s': %s", - host_port, gai_strerror(ret)); - return NULL; - } - - return ai; -} diff --git a/src/resolver.h b/src/resolver.h deleted file mode 100644 index e5ad06754..000000000 --- a/src/resolver.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_RESOLVER_H -#define MPD_RESOLVER_H - -#include <glib.h> - -struct sockaddr; -struct addrinfo; - -G_GNUC_CONST -static inline GQuark -resolver_quark(void) -{ - return g_quark_from_static_string("resolver"); -} - -/** - * Converts the specified socket address into a string in the form - * "IP:PORT". The return value must be freed with g_free() when you - * don't need it anymore. - * - * @param sa the sockaddr struct - * @param length the length of #sa in bytes - * @param error location to store the error occurring, or NULL to - * ignore errors - */ -G_GNUC_MALLOC -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); - -/** - * Resolve a specification in the form "host", "host:port", - * "[host]:port". This is a convenience wrapper for getaddrinfo(). - * - * @param default_port a default port number that will be used if none - * is given in the string (if applicable); pass 0 to go without a - * default - * @return an #addrinfo linked list that must be freed with - * freeaddrinfo(), or NULL on error - */ -struct addrinfo * -resolve_host_port(const char *host_port, unsigned default_port, - int flags, int socktype, - GError **error_r); - -#endif diff --git a/src/riff.c b/src/riff.c deleted file mode 100644 index 9ee916971..000000000 --- a/src/riff.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "riff.h" - -#include <glib.h> - -#include <stdint.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "riff" - -struct riff_header { - char id[4]; - uint32_t size; - char format[4]; -}; - -struct riff_chunk_header { - char id[4]; - uint32_t size; -}; - -size_t -riff_seek_id3(FILE *file) -{ - int ret; - struct stat st; - struct riff_header header; - struct riff_chunk_header chunk; - size_t size; - - /* determine the file size */ - - ret = fstat(fileno(file), &st); - if (ret < 0) { - g_warning("Failed to stat file descriptor: %s", - g_strerror(errno)); - return 0; - } - - /* seek to the beginning and read the RIFF header */ - - ret = fseek(file, 0, SEEK_SET); - if (ret != 0) { - g_warning("Failed to seek: %s", g_strerror(errno)); - return 0; - } - - size = fread(&header, sizeof(header), 1, file); - if (size != 1 || - memcmp(header.id, "RIFF", 4) != 0 || - GUINT32_FROM_LE(header.size) > (uint32_t)st.st_size) - /* not a RIFF file */ - return 0; - - while (true) { - /* read the chunk header */ - - size = fread(&chunk, sizeof(chunk), 1, file); - if (size != 1) - return 0; - - size = GUINT32_FROM_LE(chunk.size); - if (size > G_MAXINT32) - /* too dangerous, bail out: possible integer - underflow when casting to off_t */ - return 0; - - if (size % 2 != 0) - /* pad byte */ - ++size; - - if (memcmp(chunk.id, "id3 ", 4) == 0) - /* found it! */ - return size; - - ret = fseek(file, size, SEEK_CUR); - if (ret != 0) - return 0; - } -} diff --git a/src/riff.h b/src/riff.h deleted file mode 100644 index 7b35e092a..000000000 --- a/src/riff.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A parser for the RIFF file format (e.g. WAV). - */ - -#ifndef MPD_RIFF_H -#define MPD_RIFF_H - -#include <stdbool.h> -#include <stddef.h> -#include <stdio.h> - -/** - * Seeks the RIFF file to the ID3 chunk. - * - * @return the size of the ID3 chunk on success, or 0 if this is not a - * RIFF file or no ID3 chunk was found - */ -size_t -riff_seek_id3(FILE *file); - -#endif diff --git a/src/server_socket.c b/src/server_socket.c deleted file mode 100644 index 396399596..000000000 --- a/src/server_socket.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" - -#ifdef HAVE_STRUCT_UCRED -#define _GNU_SOURCE 1 -#endif - -#include "server_socket.h" -#include "socket_util.h" -#include "resolver.h" -#include "fd_util.h" -#include "glib_socket.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 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) { - if (socket_keepalive(fd)) - g_warning("Could not set TCP keepalive option: %s", - g_strerror(errno)); - s->parent->callback(fd, (const struct sockaddr*)&address, - address_length, get_remote_uid(fd), - s->parent->callback_ctx); - } else { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - return true; -} - -static void -set_fd(struct one_socket *s, int fd) -{ - assert(s != NULL); - assert(s->fd < 0); - assert(fd >= 0); - - s->fd = fd; - - GIOChannel *channel = g_io_channel_new_socket(s->fd); - s->source_id = g_io_add_watch(channel, G_IO_IN, - server_socket_in_event, s); - g_io_channel_unref(channel); -} - -bool -server_socket_open(struct server_socket *ss, GError **error_r) -{ - 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; - int fd = socket_bind_listen(s->address.sa_family, - SOCK_STREAM, 0, - &s->address, s->address_length, 5, - &error); - if (fd < 0) { - if (good != NULL && good->serial == s->serial) { - char *address_string = one_socket_to_string(s); - char *good_string = one_socket_to_string(good); - 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 */ - - set_fd(s, fd); - - /* 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_socket(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; -} - -bool -server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r) -{ - assert(ss != NULL); - assert(ss->sockets_tail_r != NULL); - assert(*ss->sockets_tail_r == NULL); - assert(fd >= 0); - - struct sockaddr_storage address; - socklen_t address_length; - if (getsockname(fd, (struct sockaddr *)&address, - &address_length) < 0) { - g_set_error(error_r, server_socket_quark(), errno, - "Failed to get socket address: %s", - g_strerror(errno)); - return false; - } - - struct one_socket *s = one_socket_new(ss->next_serial, - (struct sockaddr *)&address, - address_length); - s->parent = ss; - *ss->sockets_tail_r = s; - ss->sockets_tail_r = &s->next; - - set_fd(s, fd); - - return true; -} - -static struct one_socket * -server_socket_add_address(struct server_socket *ss, - const struct sockaddr *address, - 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 *ai = resolve_host_port(hostname, port, - AI_PASSIVE, SOCK_STREAM, - error_r); - if (ai == NULL) - return false; - - for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) - server_socket_add_address(ss, i->ai_addr, i->ai_addrlen); - - 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 deleted file mode 100644 index 7caa4bbf2..000000000 --- a/src/server_socket.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 socket descriptor that is accepting connections. After this - * has been called, don't call server_socket_open(), because the - * socket is already open. - */ -bool -server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r); - -/** - * Add a listener on a port on all interfaces. - * - * @param port the TCP port - * @param error_r location to store the error occurring, 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 occurring, 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 occurring, 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 deleted file mode 100644 index b23f9e778..000000000 --- a/src/sig_handlers.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "sig_handlers.h" - -#ifndef WIN32 - -#include "log.h" -#include "main.h" -#include "event_pipe.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <signal.h> -#include <errno.h> -#include <string.h> - -static void exit_signal_handler(G_GNUC_UNUSED int signum) -{ - g_main_loop_quit(main_loop); -} - -static void reload_signal_handler(G_GNUC_UNUSED int signum) -{ - event_pipe_emit_fast(PIPE_EVENT_RELOAD); -} - -static void -x_sigaction(int signum, const struct sigaction *act) -{ - if (sigaction(signum, act, NULL) < 0) - MPD_ERROR("sigaction() failed: %s", strerror(errno)); -} - -static void -handle_reload_event(void) -{ - g_debug("got SIGHUP, reopening log files"); - cycle_log_files(); -} - -#endif - -void initSigHandlers(void) -{ -#ifndef WIN32 - struct sigaction sa; - - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sa.sa_handler = SIG_IGN; - x_sigaction(SIGPIPE, &sa); - - sa.sa_handler = exit_signal_handler; - x_sigaction(SIGINT, &sa); - x_sigaction(SIGTERM, &sa); - - event_pipe_register(PIPE_EVENT_RELOAD, handle_reload_event); - sa.sa_handler = reload_signal_handler; - x_sigaction(SIGHUP, &sa); -#endif -} diff --git a/src/sig_handlers.h b/src/sig_handlers.h deleted file mode 100644 index 32e9bad95..000000000 --- a/src/sig_handlers.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIG_HANDLERS_H -#define MPD_SIG_HANDLERS_H - -void initSigHandlers(void); - -#endif diff --git a/src/socket_util.c b/src/socket_util.c deleted file mode 100644 index a06a0cbd5..000000000 --- a/src/socket_util.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "socket_util.h" -#include "fd_util.h" - -#include <errno.h> -#include <unistd.h> - -#ifndef G_OS_WIN32 -#include <sys/socket.h> -#else /* G_OS_WIN32 */ -#include <ws2tcpip.h> -#include <winsock.h> -#endif /* G_OS_WIN32 */ - -#ifdef HAVE_IPV6 -#include <string.h> -#endif - -static GQuark -listen_quark(void) -{ - return g_quark_from_static_string("listen"); -} - -int -socket_bind_listen(int domain, int type, int protocol, - const struct sockaddr *address, size_t address_length, - int backlog, - GError **error) -{ - int fd, ret; - const int reuse = 1; - - 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; - } - - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, - (const char *) &reuse, sizeof(reuse)); - if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "setsockopt() failed: %s", g_strerror(errno)); - close_socket(fd); - return -1; - } - - ret = bind(fd, address, address_length); - if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "%s", g_strerror(errno)); - close_socket(fd); - return -1; - } - - ret = listen(fd, backlog); - if (ret < 0) { - g_set_error(error, listen_quark(), errno, - "listen() failed: %s", g_strerror(errno)); - close_socket(fd); - return -1; - } - -#ifdef HAVE_STRUCT_UCRED - setsockopt(fd, SOL_SOCKET, SO_PASSCRED, - (const char *) &reuse, sizeof(reuse)); -#endif - - return fd; -} - -int -socket_keepalive(int fd) -{ - const int reuse = 1; - - return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, - (const char *)&reuse, sizeof(reuse)); -} diff --git a/src/socket_util.h b/src/socket_util.h deleted file mode 100644 index 93bd27362..000000000 --- a/src/socket_util.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This library provides easy helper functions for working with - * sockets. - * - */ - -#ifndef SOCKET_UTIL_H -#define SOCKET_UTIL_H - -#include <glib.h> - -struct sockaddr; - -/** - * Creates a socket listening on the specified address. This is a - * shortcut for socket(), bind() and listen(). - * - * @param domain the socket domain, e.g. PF_INET6 - * @param type the socket type, e.g. SOCK_STREAM - * @param protocol the protocol, usually 0 to let the kernel choose - * @param address the address to listen on - * @param address_length the size of #address - * @param backlog the backlog parameter for the listen() system call - * @param error location to store the error occurring, or NULL to - * ignore errors - * @return the socket file descriptor or -1 on error - */ -int -socket_bind_listen(int domain, int type, int protocol, - const struct sockaddr *address, size_t address_length, - int backlog, - GError **error); - -int -socket_keepalive(int fd); - -#endif diff --git a/src/song.c b/src/song.c deleted file mode 100644 index f5cc7c35a..000000000 --- a/src/song.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song.h" -#include "uri.h" -#include "directory.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> - -static struct song * -song_alloc(const char *uri, struct directory *parent) -{ - size_t uri_length; - struct song *song; - - 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->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 *uri) -{ - return song_alloc(uri, NULL); -} - -struct song * -song_file_new(const char *path, struct directory *parent) -{ - assert((parent == NULL) == (*path == '/')); - - return song_alloc(path, parent); -} - -struct song * -song_replace_uri(struct song *old_song, const char *uri) -{ - struct song *new_song = song_alloc(uri, old_song->parent); - new_song->tag = old_song->tag; - new_song->mtime = old_song->mtime; - new_song->start_ms = old_song->start_ms; - new_song->end_ms = old_song->end_ms; - g_free(old_song); - return new_song; -} - -void -song_free(struct song *song) -{ - if (song->tag) - tag_free(song->tag); - g_free(song); -} - -char * -song_get_uri(const struct song *song) -{ - assert(song != NULL); - assert(*song->uri); - - if (!song_in_database(song) || directory_is_root(song->parent)) - return g_strdup(song->uri); - else - return g_strconcat(directory_get_path(song->parent), - "/", 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 deleted file mode 100644 index 8b97d45d0..000000000 --- a/src/song.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_H -#define MPD_SONG_H - -#include "util/list.h" - -#include <stddef.h> -#include <stdbool.h> -#include <sys/time.h> - -#define SONG_FILE "file: " -#define SONG_TIME "Time: " - -struct song { - /** - * Pointers to the siblings of this directory within the - * parent directory. It is unused (undefined) if this song is - * not in the database. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head siblings; - - struct tag *tag; - struct directory *parent; - time_t mtime; - - /** - * 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 *uri); - -/** allocate a new song with a local file name */ -struct song * -song_file_new(const char *path, struct directory *parent); - -/** - * allocate a new song structure with a local file name and attempt to - * load its metadata. If all decoder plugin fail to read its meta - * data, NULL is returned. - */ -struct song * -song_file_load(const char *path, struct directory *parent); - -/** - * Replaces the URI of a song object. The given song object is - * destroyed, and a newly allocated one is returned. It does not - * update the reference within the parent directory; the caller is - * responsible for doing that. - */ -struct song * -song_replace_uri(struct song *song, const char *uri); - -void -song_free(struct song *song); - -bool -song_file_update(struct song *song); - -bool -song_file_update_inarchive(struct song *song); - -/** - * Returns the URI of the song in UTF-8 encoding, including its - * location within the music directory. - * - * The return value is allocated on the heap, and must be freed by the - * caller. - */ -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) -{ - return song->parent != NULL; -} - -static inline bool -song_is_file(const struct song *song) -{ - return song_in_database(song) || song->uri[0] == '/'; -} - -#endif diff --git a/src/song_print.c b/src/song_print.c deleted file mode 100644 index fb608a8b2..000000000 --- a/src/song_print.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_print.h" -#include "song.h" -#include "directory.h" -#include "tag_print.h" -#include "client.h" -#include "uri.h" -#include "mapper.h" - -void -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->uri); - } else { - char *allocated; - const char *uri; - - uri = allocated = uri_remove_auth(song->uri); - if (uri == NULL) - uri = song->uri; - - client_printf(client, "%s%s\n", SONG_FILE, - map_to_relative_path(uri)); - - g_free(allocated); - } -} - -void -song_print_info(struct client *client, struct song *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); -} diff --git a/src/song_print.h b/src/song_print.h deleted file mode 100644 index 8f1f0cc65..000000000 --- a/src/song_print.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_PRINT_H -#define MPD_SONG_PRINT_H - -struct client; -struct song; -struct songvec; - -void -song_print_info(struct client *client, struct song *song); - -void -song_print_uri(struct client *client, struct song *song); - -#endif diff --git a/src/song_save.c b/src/song_save.c deleted file mode 100644 index 4fcb46e22..000000000 --- a/src/song_save.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_save.h" -#include "song.h" -#include "tag_save.h" -#include "directory.h" -#include "tag.h" -#include "text_file.h" -#include "string_util.h" - -#include <glib.h> - -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "song" - -#define SONG_MTIME "mtime" -#define SONG_END "song_end" - -static GQuark -song_save_quark(void) -{ - return g_quark_from_static_string("song_save"); -} - -void -song_save(FILE *fp, const struct song *song) -{ - fprintf(fp, SONG_BEGIN "%s\n", song->uri); - - 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); - fprintf(fp, SONG_END "\n"); -} - -struct song * -song_load(FILE *fp, struct directory *parent, const char *uri, - GString *buffer, GError **error_r) -{ - struct song *song = parent != NULL - ? song_file_new(uri, parent) - : song_remote_new(uri); - char *line, *colon; - enum tag_type type; - const char *value; - - 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; - } - - *colon++ = 0; - value = strchug_fast_c(colon); - - 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, type, value); - } else if (strcmp(line, "Time") == 0) { - if (!song->tag) { - song->tag = tag_new(); - tag_begin_add(song->tag); - } - - song->tag->time = atoi(value); - } else if (strcmp(line, "Playlist") == 0) { - if (!song->tag) { - song->tag = tag_new(); - tag_begin_add(song->tag); - } - - song->tag->has_playlist = strcmp(value, "yes") == 0; - } else if (strcmp(line, SONG_MTIME) == 0) { - song->mtime = atoi(value); - } else if (strcmp(line, "Range") == 0) { - 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; - } - } - - if (song->tag != NULL) - tag_end_add(song->tag); - - return song; -} diff --git a/src/song_save.h b/src/song_save.h deleted file mode 100644 index f6ecbbfeb..000000000 --- a/src/song_save.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_SAVE_H -#define MPD_SONG_SAVE_H - -#include <glib.h> - -#include <stdio.h> - -#define SONG_BEGIN "song_begin: " - -struct song; -struct directory; - -void -song_save(FILE *fp, const struct song *song); - -/** - * Loads a song from the input file. Reading stops after the - * "song_end" line. - * - * @param error_r location to store the error occurring, 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_sort.c b/src/song_sort.c deleted file mode 100644 index 397d2c7a9..000000000 --- a/src/song_sort.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_sort.h" -#include "song.h" -#include "util/list.h" -#include "util/list_sort.h" -#include "tag.h" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -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. - */ -static int -compare_number_string(const char *a, const char *b) -{ - long ai = a == NULL ? 0 : strtol(a, NULL, 10); - long bi = b == NULL ? 0 : strtol(b, NULL, 10); - - if (ai <= 0) - return bi <= 0 ? 0 : -1; - - if (bi <= 0) - return 1; - - return ai - bi; -} - -static int -compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type 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 */ -static int -song_cmp(G_GNUC_UNUSED void *priv, struct list_head *_a, struct list_head *_b) -{ - const struct song *a = (const struct song *)_a; - const struct song *b = (const struct song *)_b; - int ret; - - /* 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_TRACK); - if (ret != 0) - return ret; - - /* still no difference? compare file name */ - return g_utf8_collate(a->uri, b->uri); -} - -void -song_list_sort(struct list_head *songs) -{ - list_sort(NULL, songs, song_cmp); -} diff --git a/src/song_sort.h b/src/song_sort.h deleted file mode 100644 index ec124cf4a..000000000 --- a/src/song_sort.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_SORT_H -#define MPD_SONG_SORT_H - -struct list_head; - -void -song_list_sort(struct list_head *songs); - -#endif diff --git a/src/song_sticker.c b/src/song_sticker.c deleted file mode 100644 index 78025906e..000000000 --- a/src/song_sticker.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "song_sticker.h" -#include "song.h" -#include "directory.h" -#include "sticker.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -char * -sticker_song_get_value(const struct song *song, const char *name) -{ - char *uri, *value; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - value = sticker_load_value("song", uri, name); - g_free(uri); - - return value; -} - -bool -sticker_song_set_value(const struct song *song, - const char *name, const char *value) -{ - char *uri; - bool ret; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - ret = sticker_store_value("song", uri, name, value); - g_free(uri); - - return ret; -} - -bool -sticker_song_delete(const struct song *song) -{ - char *uri; - bool ret; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - ret = sticker_delete("song", uri); - g_free(uri); - - return ret; -} - -bool -sticker_song_delete_value(const struct song *song, const char *name) -{ - char *uri; - bool success; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - success = sticker_delete_value("song", uri, name); - g_free(uri); - - return success; -} - -struct sticker * -sticker_song_get(const struct song *song) -{ - char *uri; - struct sticker *sticker; - - assert(song != NULL); - assert(song_in_database(song)); - - uri = song_get_uri(song); - sticker = sticker_load("song", uri); - g_free(uri); - - return sticker; -} - -struct sticker_song_find_data { - struct directory *directory; - const char *base_uri; - size_t base_uri_length; - - void (*func)(struct song *song, const char *value, - gpointer user_data); - gpointer user_data; -}; - -static void -sticker_song_find_cb(const char *uri, const char *value, gpointer user_data) -{ - struct sticker_song_find_data *data = user_data; - struct song *song; - - if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) - /* should not happen, ignore silently */ - return; - - song = directory_lookup_song(data->directory, - uri + data->base_uri_length); - if (song != NULL) - data->func(song, value, data->user_data); -} - -bool -sticker_song_find(struct directory *directory, const char *name, - void (*func)(struct song *song, const char *value, - gpointer user_data), - gpointer user_data) -{ - struct sticker_song_find_data data = { - .directory = directory, - .func = func, - .user_data = user_data, - }; - char *allocated; - bool success; - - data.base_uri = directory_get_path(directory); - if (*data.base_uri != 0) - /* append slash to base_uri */ - data.base_uri = allocated = - g_strconcat(data.base_uri, "/", NULL); - else - /* searching in root directory - no trailing slash */ - allocated = NULL; - - data.base_uri_length = strlen(data.base_uri); - - success = sticker_find("song", data.base_uri, name, - sticker_song_find_cb, &data); - g_free(allocated); - - return success; -} diff --git a/src/song_sticker.h b/src/song_sticker.h deleted file mode 100644 index 20ae68ce9..000000000 --- a/src/song_sticker.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef SONG_STICKER_H -#define SONG_STICKER_H - -#include <stdbool.h> -#include <glib.h> - -struct song; -struct directory; -struct sticker; - -/** - * Returns one value from a song's sticker record. The caller must - * free the return value with g_free(). - */ -char * -sticker_song_get_value(const struct song *song, const char *name); - -/** - * Sets a sticker value in the specified song. Overwrites existing - * values. - */ -bool -sticker_song_set_value(const struct song *song, - const char *name, const char *value); - -/** - * Deletes a sticker from the database. All values are deleted. - */ -bool -sticker_song_delete(const struct song *song); - -/** - * Deletes a sticker value. Does nothing if the sticker did not - * exist. - */ -bool -sticker_song_delete_value(const struct song *song, const char *name); - -/** - * Loads the sticker for the specified song. - * - * @param song the song object - * @return a sticker object, or NULL on error or if there is no sticker - */ -struct sticker * -sticker_song_get(const struct song *song); - -/** - * Finds stickers with the specified name below the specified - * directory. - * - * Caller must lock the #db_mutex. - * - * @param directory the base directory to search in - * @param name the name of the sticker - * @return true on success (even if no sticker was found), false on - * failure - */ -bool -sticker_song_find(struct directory *directory, const char *name, - void (*func)(struct song *song, const char *value, - gpointer user_data), - gpointer user_data); - -#endif diff --git a/src/song_update.c b/src/song_update.c deleted file mode 100644 index f9d6b6f46..000000000 --- a/src/song_update.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* 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 "tag_handler.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 bool -tag_scan_fallback(const char *path, - const struct tag_handler *handler, void *handler_ctx) -{ - return tag_ape_scan2(path, handler, handler_ctx) || - tag_id3_scan(path, handler, handler_ctx); -} - -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; - - GMutex *mutex = NULL; - GCond *cond; -#if !GCC_CHECK_VERSION(4, 2) - /* work around "may be used uninitialized in this function" - false positive */ - cond = NULL; -#endif - - do { - /* load file tag */ - song->tag = tag_new(); - if (decoder_plugin_scan_file(plugin, path_fs, - &full_tag_handler, song->tag)) - break; - - tag_free(song->tag); - song->tag = NULL; - - /* fall back to stream tag */ - if (plugin->scan_stream != NULL) { - /* open the input_stream (if not already - open) */ - if (is == NULL) { - mutex = g_mutex_new(); - cond = g_cond_new(); - is = input_stream_open(path_fs, mutex, cond, - NULL); - } - - /* now try the stream_tag() method */ - if (is != NULL) { - song->tag = tag_new(); - if (decoder_plugin_scan_stream(plugin, is, - &full_tag_handler, - song->tag)) - break; - - tag_free(song->tag); - song->tag = NULL; - - input_stream_lock_seek(is, 0, SEEK_SET, NULL); - } - } - - plugin = decoder_plugin_from_suffix(suffix, plugin); - } while (plugin != NULL); - - if (is != NULL) - input_stream_close(is); - - if (mutex != NULL) { - g_cond_free(cond); - g_mutex_free(mutex); - } - - if (song->tag != NULL && tag_is_empty(song->tag)) - tag_scan_fallback(path_fs, &full_tag_handler, 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, NULL); - if (plugin == NULL) - return false; - - if (song->tag != NULL) - tag_free(song->tag); - - //accept every file that has music suffix - //because we don't support tag reading through - //input streams - song->tag = tag_new(); - - return true; -} diff --git a/src/state_file.c b/src/state_file.c deleted file mode 100644 index de0e70538..000000000 --- a/src/state_file.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "state_file.h" -#include "output_state.h" -#include "playlist.h" -#include "playlist_state.h" -#include "volume.h" -#include "text_file.h" - -#include <glib.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "state_file" - -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(struct player_control *pc) -{ - FILE *fp; - - assert(state_file_path != NULL); - - g_debug("Saving state file %s", state_file_path); - - fp = fopen(state_file_path, "w"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to create %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - save_sw_volume_state(fp); - audio_output_state_save(fp); - playlist_state_save(fp, &g_playlist, pc); - - fclose(fp); - - prev_volume_version = sw_volume_state_get_hash(); - prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); -} - -static void -state_file_read(struct player_control *pc) -{ - FILE *fp; - bool success; - - assert(state_file_path != NULL); - - g_debug("Loading state file %s", state_file_path); - - fp = fopen(state_file_path, "r"); - if (G_UNLIKELY(!fp)) { - g_warning("failed to open %s: %s", - state_file_path, g_strerror(errno)); - return; - } - - 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, pc); - if (!success) - g_warning("Unrecognized line in state file: %s", line); - } - - 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, pc); - - - g_string_free(buffer, true); -} - -/** - * This function is called every 5 minutes by the GLib main loop, and - * saves the state file. - */ -static gboolean -timer_save_state_file(gpointer data) -{ - struct player_control *pc = data; - - if (prev_volume_version == sw_volume_state_get_hash() && - prev_output_version == audio_output_state_get_version() && - prev_playlist_version == playlist_state_get_hash(&g_playlist, pc)) - /* nothing has changed - don't save the state file, - don't spin up the hard disk */ - return true; - - state_file_write(pc); - return true; -} - -void -state_file_init(const char *path, struct player_control *pc) -{ - assert(state_file_path == NULL); - - if (path == NULL) - return; - - state_file_path = g_strdup(path); - state_file_read(pc); - - save_state_source_id = g_timeout_add_seconds(5 * 60, - timer_save_state_file, - pc); -} - -void -state_file_finish(struct player_control *pc) -{ - 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); - - state_file_write(pc); - - g_free(state_file_path); -} diff --git a/src/state_file.h b/src/state_file.h deleted file mode 100644 index 4c4f881cc..000000000 --- a/src/state_file.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STATE_FILE_H -#define MPD_STATE_FILE_H - -struct player_control; - -void -state_file_init(const char *path, struct player_control *pc); - -void -state_file_finish(struct player_control *pc); - -void write_state_file(void); - -#endif /* STATE_FILE_H */ diff --git a/src/stats.c b/src/stats.c deleted file mode 100644 index fe6a064a6..000000000 --- a/src/stats.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "stats.h" -#include "database.h" -#include "db_visitor.h" -#include "tag.h" -#include "song.h" -#include "client.h" -#include "player_control.h" -#include "strset.h" -#include "client_internal.h" - -struct stats stats; - -void stats_global_init(void) -{ - stats.timer = g_timer_new(); -} - -void stats_global_finish(void) -{ - g_timer_destroy(stats.timer); -} - -struct visit_data { - struct strset *artists; - struct strset *albums; -}; - -static void -visit_tag(struct visit_data *data, const struct tag *tag) -{ - if (tag->time > 0) - stats.song_duration += tag->time; - - for (unsigned i = 0; i < tag->num_items; ++i) { - const struct tag_item *item = tag->items[i]; - - switch (item->type) { - case TAG_ARTIST: - strset_add(data->artists, item->value); - break; - - case TAG_ALBUM: - strset_add(data->albums, item->value); - break; - - default: - break; - } - } -} - -static bool -collect_stats_song(struct song *song, void *_data, - G_GNUC_UNUSED GError **error_r) -{ - struct visit_data *data = _data; - - ++stats.song_count; - - if (song->tag != NULL) - visit_tag(data, song->tag); - - return true; -} - -static const struct db_visitor collect_stats_visitor = { - .song = collect_stats_song, -}; - -void stats_update(void) -{ - struct visit_data data; - - stats.song_count = 0; - stats.song_duration = 0; - stats.artist_count = 0; - - data.artists = strset_new(); - data.albums = strset_new(); - - db_walk("", &collect_stats_visitor, &data, NULL); - - stats.artist_count = strset_size(data.artists); - stats.album_count = strset_size(data.albums); - - strset_free(data.artists); - strset_free(data.albums); -} - -int stats_print(struct client *client) -{ - client_printf(client, - "artists: %u\n" - "albums: %u\n" - "songs: %i\n" - "uptime: %li\n" - "playtime: %li\n" - "db_playtime: %li\n" - "db_update: %li\n", - stats.artist_count, - stats.album_count, - stats.song_count, - (long)g_timer_elapsed(stats.timer, NULL), - (long)(pc_get_total_play_time(client->player_control) + 0.5), - stats.song_duration, - (long)db_get_mtime()); - return 0; -} diff --git a/src/stats.h b/src/stats.h deleted file mode 100644 index a686477de..000000000 --- a/src/stats.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STATS_H -#define MPD_STATS_H - -#include <glib.h> - -struct client; - -struct stats { - GTimer *timer; - - /** number of song files in the music directory */ - unsigned song_count; - - /** sum of all song durations in the music directory (in - seconds) */ - unsigned long song_duration; - - /** number of distinct artist names in the music directory */ - unsigned artist_count; - - /** number of distinct album names in the music directory */ - unsigned album_count; -}; - -extern struct stats stats; - -void stats_global_init(void); - -void stats_global_finish(void); - -void stats_update(void); - -int stats_print(struct client *client); - -#endif diff --git a/src/sticker.c b/src/sticker.c deleted file mode 100644 index 346a827a5..000000000 --- a/src/sticker.c +++ /dev/null @@ -1,660 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "sticker.h" -#include "idle.h" - -#include <glib.h> -#include <sqlite3.h> -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "sticker" - -#if SQLITE_VERSION_NUMBER < 3003009 -#define sqlite3_prepare_v2 sqlite3_prepare -#endif - -struct sticker { - GHashTable *table; -}; - -enum sticker_sql { - STICKER_SQL_GET, - STICKER_SQL_LIST, - STICKER_SQL_UPDATE, - STICKER_SQL_INSERT, - STICKER_SQL_DELETE, - STICKER_SQL_DELETE_VALUE, - STICKER_SQL_FIND, -}; - -static const char *const sticker_sql[] = { - [STICKER_SQL_GET] = - "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_LIST] = - "SELECT name,value FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_UPDATE] = - "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_INSERT] = - "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", - [STICKER_SQL_DELETE] = - "DELETE FROM sticker WHERE type=? AND uri=?", - [STICKER_SQL_DELETE_VALUE] = - "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - [STICKER_SQL_FIND] = - "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", -}; - -static const char sticker_sql_create[] = - "CREATE TABLE IF NOT EXISTS sticker(" - " type VARCHAR NOT NULL, " - " uri VARCHAR NOT NULL, " - " name VARCHAR NOT NULL, " - " value VARCHAR NOT NULL" - ");" - "CREATE UNIQUE INDEX IF NOT EXISTS" - " sticker_value ON sticker(type, uri, name);" - ""; - -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, GError **error_r) -{ - int ret; - sqlite3_stmt *stmt; - - ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL); - 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; -} - -bool -sticker_global_init(const char *path, GError **error_r) -{ - int ret; - - if (path == NULL) - /* not configured */ - return true; - - /* open/create the sqlite database */ - - ret = sqlite3_open(path, &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_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], error_r); - if (sticker_stmt[i] == NULL) - return false; - } - - return true; -} - -void -sticker_global_finish(void) -{ - if (sticker_db == NULL) - /* not configured */ - return; - - for (unsigned i = 0; i < G_N_ELEMENTS(sticker_stmt); ++i) { - assert(sticker_stmt[i] != NULL); - - sqlite3_finalize(sticker_stmt[i]); - } - - sqlite3_close(sticker_db); -} - -bool -sticker_enabled(void) -{ - return sticker_db != NULL; -} - -char * -sticker_load_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; - int ret; - char *value; - - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - - if (*name == 0) - return NULL; - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret == SQLITE_ROW) { - /* record found */ - value = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - } else if (ret == SQLITE_DONE) { - /* no record found */ - value = NULL; - } else { - /* error */ - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return NULL; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return value; -} - -static bool -sticker_list_values(GHashTable *hash, const char *type, const char *uri) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; - int ret; - char *name, *value; - - assert(hash != NULL); - assert(type != NULL); - assert(uri != NULL); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - case SQLITE_ROW: - name = g_strdup((const char*)sqlite3_column_text(stmt, 0)); - value = g_strdup((const char*)sqlite3_column_text(stmt, 1)); - g_hash_table_insert(hash, name, value); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - } while (ret != SQLITE_DONE); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return true; -} - -static bool -sticker_update_value(const char *type, const char *uri, - const char *name, const char *value) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; - int ret; - - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - assert(*name != 0); - assert(value != NULL); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, value, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_changes(sticker_db); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return ret > 0; -} - -static bool -sticker_insert_value(const char *type, const char *uri, - const char *name, const char *value) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; - int ret; - - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - assert(*name != 0); - assert(value != NULL); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, value, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - - idle_add(IDLE_STICKER); - return true; -} - -bool -sticker_store_value(const char *type, const char *uri, - const char *name, const char *value) -{ - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - assert(name != NULL); - assert(value != NULL); - - if (*name == 0) - return false; - - return sticker_update_value(type, uri, name, value) || - sticker_insert_value(type, uri, name, value); -} - -bool -sticker_delete(const char *type, const char *uri) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; - int ret; - - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return true; -} - -bool -sticker_delete_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; - int ret; - - assert(sticker_enabled()); - assert(type != NULL); - assert(uri != NULL); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_changes(sticker_db); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return ret > 0; -} - -static struct sticker * -sticker_new(void) -{ - struct sticker *sticker = g_new(struct sticker, 1); - - sticker->table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - return sticker; -} - -void -sticker_free(struct sticker *sticker) -{ - assert(sticker != NULL); - assert(sticker->table != NULL); - - g_hash_table_destroy(sticker->table); - g_free(sticker); -} - -const char * -sticker_get_value(const struct sticker *sticker, const char *name) -{ - return g_hash_table_lookup(sticker->table, name); -} - -struct sticker_foreach_data { - void (*func)(const char *name, const char *value, - gpointer user_data); - gpointer user_data; -}; - -static void -sticker_foreach_func(gpointer key, gpointer value, gpointer user_data) -{ - struct sticker_foreach_data *data = user_data; - - data->func(key, value, data->user_data); -} - -void -sticker_foreach(const struct sticker *sticker, - void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data) -{ - struct sticker_foreach_data data = { - .func = func, - .user_data = user_data, - }; - - g_hash_table_foreach(sticker->table, sticker_foreach_func, &data); -} - -struct sticker * -sticker_load(const char *type, const char *uri) -{ - struct sticker *sticker = sticker_new(); - bool success; - - success = sticker_list_values(sticker->table, type, uri); - if (!success) { - sticker_free(sticker); - return NULL; - } - - if (g_hash_table_size(sticker->table) == 0) { - /* don't return empty sticker objects */ - sticker_free(sticker); - return NULL; - } - - return sticker; -} - -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; - int ret; - - assert(type != NULL); - assert(name != NULL); - assert(func != NULL); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - if (base_uri == NULL) - base_uri = ""; - - ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, NULL); - if (ret != SQLITE_OK) { - g_warning("sqlite3_bind_text() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - case SQLITE_ROW: - func((const char*)sqlite3_column_text(stmt, 0), - (const char*)sqlite3_column_text(stmt, 1), - user_data); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - g_warning("sqlite3_step() failed: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - } while (ret != SQLITE_DONE); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return true; -} diff --git a/src/sticker.h b/src/sticker.h deleted file mode 100644 index 5545206a5..000000000 --- a/src/sticker.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This is the sticker database library. It is the backend of all the - * sticker code in MPD. - * - * "Stickers" are pieces of information attached to existing MPD - * objects (e.g. song files, directories, albums). Clients can create - * arbitrary name/value pairs. MPD itself does not assume any special - * meaning in them. - * - * The goal is to allow clients to share additional (possibly dynamic) - * information about songs, which is neither stored on the client (not - * available to other clients), nor stored in the song files (MPD has - * no write access). - * - * Client developers should create a standard for common sticker - * names, to ensure interoperability. - * - * Examples: song ratings; statistics; deferred tag writes; lyrics; - * ... - * - */ - -#ifndef STICKER_H -#define STICKER_H - -#include <glib.h> - -#include <stdbool.h> - -struct sticker; - -/** - * Opens the sticker database (if path is not NULL). - * - * @param error_r location to store the error occurring, or NULL to - * ignore errors - * @return true on success, false on error - */ -bool -sticker_global_init(const char *path, GError **error_r); - -/** - * Close the sticker database. - */ -void -sticker_global_finish(void); - -/** - * Returns true if the sticker database is configured and available. - */ -bool -sticker_enabled(void); - -/** - * Returns one value from an object's sticker record. The caller must - * free the return value with g_free(). - */ -char * -sticker_load_value(const char *type, const char *uri, const char *name); - -/** - * Sets a sticker value in the specified object. Overwrites existing - * values. - */ -bool -sticker_store_value(const char *type, const char *uri, - const char *name, const char *value); - -/** - * Deletes a sticker from the database. All sticker values of the - * specified object are deleted. - */ -bool -sticker_delete(const char *type, const char *uri); - -/** - * Deletes a sticker value. Fails if no sticker with this name - * exists. - */ -bool -sticker_delete_value(const char *type, const char *uri, const char *name); - -/** - * Frees resources held by the sticker object. - * - * @param sticker the sticker object to be freed - */ -void -sticker_free(struct sticker *sticker); - -/** - * Determines a single value in a sticker. - * - * @param sticker the sticker object - * @param name the name of the sticker - * @return the sticker value, or NULL if none was found - */ -const char * -sticker_get_value(const struct sticker *sticker, const char *name); - -/** - * Iterates over all sticker items in a sticker. - * - * @param sticker the sticker object - * @param func a callback function - * @param user_data an opaque pointer for the callback function - */ -void -sticker_foreach(const struct sticker *sticker, - void (*func)(const char *name, const char *value, - gpointer user_data), - gpointer user_data); - -/** - * Loads the sticker for the specified resource. - * - * @param type the resource type, e.g. "song" - * @param uri the URI of the resource, e.g. the song path - * @return a sticker object, or NULL on error or if there is no sticker - */ -struct sticker * -sticker_load(const char *type, const char *uri); - -/** - * Finds stickers with the specified name below the specified URI. - * - * @param type the resource type, e.g. "song" - * @param base_uri the URI prefix of the resources, or NULL if all - * resources should be searched - * @param name the name of the sticker - * @return true on success (even if no sticker was found), false on - * failure - */ -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - gpointer user_data), - gpointer user_data); - -#endif diff --git a/src/sticker_print.c b/src/sticker_print.c deleted file mode 100644 index 65e79513c..000000000 --- a/src/sticker_print.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "sticker_print.h" -#include "sticker.h" -#include "client.h" - -void -sticker_print_value(struct client *client, - const char *name, const char *value) -{ - client_printf(client, "sticker: %s=%s\n", name, value); -} - -static void -print_sticker_cb(const char *name, const char *value, gpointer data) -{ - struct client *client = data; - - sticker_print_value(client, name, value); -} - -void -sticker_print(struct client *client, const struct sticker *sticker) -{ - sticker_foreach(sticker, print_sticker_cb, client); -} diff --git a/src/sticker_print.h b/src/sticker_print.h deleted file mode 100644 index 7398c8083..000000000 --- a/src/sticker_print.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STICKER_PRINT_H -#define MPD_STICKER_PRINT_H - -struct sticker; -struct client; - -/** - * Sends one sticker value to the client. - */ -void -sticker_print_value(struct client *client, - const char *name, const char *value); - -/** - * Sends all sticker values to the client. - */ -void -sticker_print(struct client *client, const struct sticker *sticker); - -#endif diff --git a/src/stored_playlist.c b/src/stored_playlist.c deleted file mode 100644 index 86b7ff8c3..000000000 --- a/src/stored_playlist.c +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "stored_playlist.h" -#include "playlist_save.h" -#include "text_file.h" -#include "song.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "database.h" -#include "idle.h" -#include "conf.h" -#include "glib_compat.h" - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> -#include <string.h> -#include <errno.h> - -static const char PLAYLIST_COMMENT = '#'; - -static unsigned playlist_max_length; -bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; - -void -spl_global_init(void) -{ - playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH); - - playlist_saveAbsolutePaths = - config_get_bool(CONF_SAVE_ABSOLUTE_PATHS, - DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS); -} - -bool -spl_valid_name(const char *name_utf8) -{ - /* - * Not supporting '/' was done out of laziness, and we should - * really strive to support it in the future. - * - * Not supporting '\r' and '\n' is done out of protocol - * limitations (and arguably laziness), but bending over head - * over heels to modify the protocol (and compatibility with - * all clients) to support idiots who put '\r' and '\n' in - * filenames isn't going to happen, either. - */ - - return strchr(name_utf8, '/') == NULL && - strchr(name_utf8, '\n') == NULL && - strchr(name_utf8, '\r') == NULL; -} - -static const char * -spl_map(GError **error_r) -{ - const char *path_fs = map_spl_path(); - if (path_fs == NULL) - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_DISABLED, - "Stored playlists are disabled"); - - return path_fs; -} - -static bool -spl_check_name(const char *name_utf8, GError **error_r) -{ - if (!spl_valid_name(name_utf8)) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_NAME, - "Bad playlist name"); - return false; - } - - return true; -} - -static char * -spl_map_to_fs(const char *name_utf8, GError **error_r) -{ - if (spl_map(error_r) == NULL || - !spl_check_name(name_utf8, error_r)) - return NULL; - - char *path_fs = map_spl_utf8_to_fs(name_utf8); - if (path_fs == NULL) - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_NAME, - "Bad playlist name"); - - return path_fs; -} - -/** - * Create a GError for the current errno. - */ -static void -playlist_errno(GError **error_r) -{ - switch (errno) { - case ENOENT: - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_LIST, - "No such playlist"); - break; - - default: - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - break; - } -} - -static struct stored_playlist_info * -load_playlist_info(const char *parent_path_fs, const char *name_fs) -{ - size_t name_length = strlen(name_fs); - char *path_fs, *name, *name_utf8; - int ret; - struct stat st; - struct stored_playlist_info *playlist; - - if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) || - memchr(name_fs, '\n', name_length) != NULL) - return NULL; - - if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX)) - return NULL; - - path_fs = g_build_filename(parent_path_fs, name_fs, NULL); - ret = stat(path_fs, &st); - g_free(path_fs); - if (ret < 0 || !S_ISREG(st.st_mode)) - return NULL; - - name = g_strndup(name_fs, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - name_utf8 = fs_charset_to_utf8(name); - g_free(name); - if (name_utf8 == NULL) - return NULL; - - playlist = g_new(struct stored_playlist_info, 1); - playlist->name = name_utf8; - playlist->mtime = st.st_mtime; - return playlist; -} - -GPtrArray * -spl_list(GError **error_r) -{ - const char *parent_path_fs = spl_map(error_r); - DIR *dir; - struct dirent *ent; - GPtrArray *list; - struct stored_playlist_info *playlist; - - if (parent_path_fs == NULL) - return NULL; - - dir = opendir(parent_path_fs); - if (dir == NULL) { - g_set_error_literal(error_r, g_file_error_quark(), errno, - g_strerror(errno)); - return NULL; - } - - list = g_ptr_array_new(); - - while ((ent = readdir(dir)) != NULL) { - playlist = load_playlist_info(parent_path_fs, ent->d_name); - if (playlist != NULL) - g_ptr_array_add(list, playlist); - } - - closedir(dir); - return list; -} - -void -spl_list_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - struct stored_playlist_info *playlist = - g_ptr_array_index(list, i); - g_free(playlist->name); - g_free(playlist); - } - - g_ptr_array_free(list, true); -} - -static bool -spl_save(GPtrArray *list, const char *utf8path, GError **error_r) -{ - FILE *file; - - assert(utf8path != NULL); - - if (spl_map(error_r) == NULL) - return false; - - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return false; - - file = fopen(path_fs, "w"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return false; - } - - for (unsigned i = 0; i < list->len; ++i) { - const char *uri = g_ptr_array_index(list, i); - playlist_print_uri(file, uri); - } - - fclose(file); - return true; -} - -GPtrArray * -spl_load(const char *utf8path, GError **error_r) -{ - FILE *file; - GPtrArray *list; - char *path_fs; - - if (spl_map(error_r) == NULL) - return NULL; - - path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return NULL; - - file = fopen(path_fs, "r"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return NULL; - } - - list = g_ptr_array_new(); - - GString *buffer = g_string_sized_new(1024); - char *s; - while ((s = read_text_line(file, buffer)) != NULL) { - if (*s == 0 || *s == PLAYLIST_COMMENT) - continue; - - if (g_path_is_absolute(s)) { - char *t = fs_charset_to_utf8(s); - if (t == NULL) - continue; - - s = g_strconcat("file://", t, NULL); - g_free(t); - } else if (!uri_has_scheme(s)) { - char *path_utf8; - - path_utf8 = map_fs_to_utf8(s); - if (path_utf8 == NULL) - continue; - - s = path_utf8; - } else { - s = fs_charset_to_utf8(s); - if (s == NULL) - continue; - } - - g_ptr_array_add(list, s); - - if (list->len >= playlist_max_length) - break; - } - - fclose(file); - return list; -} - -void -spl_free(GPtrArray *list) -{ - for (unsigned i = 0; i < list->len; ++i) { - char *uri = g_ptr_array_index(list, i); - g_free(uri); - } - - g_ptr_array_free(list, true); -} - -static char * -spl_remove_index_internal(GPtrArray *list, unsigned idx) -{ - char *uri; - - assert(idx < list->len); - - uri = g_ptr_array_remove_index(list, idx); - assert(uri != NULL); - return uri; -} - -static void -spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri) -{ - assert(idx <= list->len); - - g_ptr_array_add(list, uri); - - memmove(list->pdata + idx + 1, list->pdata + idx, - (list->len - idx - 1) * sizeof(list->pdata[0])); - g_ptr_array_index(list, idx) = uri; -} - -bool -spl_move_index(const char *utf8path, unsigned src, unsigned dest, - GError **error_r) -{ - char *uri; - - if (src == dest) - /* this doesn't check whether the playlist exists, but - what the hell.. */ - return true; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) - return false; - - if (src >= list->len || dest >= list->len) { - spl_free(list); - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_RANGE, - "Bad range"); - return false; - } - - uri = spl_remove_index_internal(list, src); - spl_insert_index_internal(list, dest, uri); - - bool result = spl_save(list, utf8path, error_r); - - spl_free(list); - - idle_add(IDLE_STORED_PLAYLIST); - return result; -} - -bool -spl_clear(const char *utf8path, GError **error_r) -{ - FILE *file; - - if (spl_map(error_r) == NULL) - return false; - - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return false; - - file = fopen(path_fs, "w"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return false; - } - - fclose(file); - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_delete(const char *name_utf8, GError **error_r) -{ - char *path_fs; - int ret; - - path_fs = spl_map_to_fs(name_utf8, error_r); - if (path_fs == NULL) - return false; - - ret = unlink(path_fs); - g_free(path_fs); - if (ret < 0) { - playlist_errno(error_r); - return false; - } - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) -{ - char *uri; - - GPtrArray *list = spl_load(utf8path, error_r); - if (list == NULL) - return false; - - if (pos >= list->len) { - spl_free(list); - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_BAD_RANGE, - "Bad range"); - return false; - } - - uri = spl_remove_index_internal(list, pos); - g_free(uri); - bool result = spl_save(list, utf8path, error_r); - - spl_free(list); - - idle_add(IDLE_STORED_PLAYLIST); - return result; -} - -bool -spl_append_song(const char *utf8path, struct song *song, GError **error_r) -{ - FILE *file; - struct stat st; - - if (spl_map(error_r) == NULL) - return false; - - char *path_fs = spl_map_to_fs(utf8path, error_r); - if (path_fs == NULL) - return false; - - file = fopen(path_fs, "a"); - g_free(path_fs); - if (file == NULL) { - playlist_errno(error_r); - return false; - } - - if (fstat(fileno(file), &st) < 0) { - playlist_errno(error_r); - fclose(file); - return false; - } - - if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { - fclose(file); - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_TOO_LARGE, - "Stored playlist is too large"); - return false; - } - - playlist_print_song(file, song); - - fclose(file); - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_append_uri(const char *url, const char *utf8file, GError **error_r) -{ - struct song *song; - - if (uri_has_scheme(url)) { - song = song_remote_new(url); - bool success = spl_append_song(utf8file, song, error_r); - song_free(song); - return success; - } else { - song = db_get_song(url); - if (song == NULL) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_SONG, - "No such song"); - return false; - } - - return spl_append_song(utf8file, song, error_r); - } -} - -static bool -spl_rename_internal(const char *from_path_fs, const char *to_path_fs, - GError **error_r) -{ - if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_NO_SUCH_LIST, - "No such playlist"); - return false; - } - - if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) { - g_set_error_literal(error_r, playlist_quark(), - PLAYLIST_RESULT_LIST_EXISTS, - "Playlist exists already"); - return false; - } - - if (rename(from_path_fs, to_path_fs) < 0) { - playlist_errno(error_r); - return false; - } - - idle_add(IDLE_STORED_PLAYLIST); - return true; -} - -bool -spl_rename(const char *utf8from, const char *utf8to, GError **error_r) -{ - if (spl_map(error_r) == NULL) - return false; - - char *from_path_fs = spl_map_to_fs(utf8from, error_r); - if (from_path_fs == NULL) - return false; - - char *to_path_fs = spl_map_to_fs(utf8to, error_r); - if (to_path_fs == NULL) { - g_free(from_path_fs); - return false; - } - - bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r); - - g_free(from_path_fs); - g_free(to_path_fs); - - return success; -} diff --git a/src/stored_playlist.h b/src/stored_playlist.h deleted file mode 100644 index cfe49633c..000000000 --- a/src/stored_playlist.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STORED_PLAYLIST_H -#define MPD_STORED_PLAYLIST_H - -#include <glib.h> -#include <stdbool.h> -#include <time.h> - -struct song; - -struct stored_playlist_info { - char *name; - - time_t mtime; -}; - -extern bool playlist_saveAbsolutePaths; - -/** - * Perform some global initialization, e.g. load configuration values. - */ -void -spl_global_init(void); - -/** - * Determines whether the specified string is a valid name for a - * stored playlist. - */ -bool -spl_valid_name(const char *name_utf8); - -/** - * Returns a list of stored_playlist_info struct pointers. Returns - * NULL if an error occurred. - */ -GPtrArray * -spl_list(GError **error_r); - -void -spl_list_free(GPtrArray *list); - -GPtrArray * -spl_load(const char *utf8path, GError **error_r); - -void -spl_free(GPtrArray *list); - -bool -spl_move_index(const char *utf8path, unsigned src, unsigned dest, - GError **error_r); - -bool -spl_clear(const char *utf8path, GError **error_r); - -bool -spl_delete(const char *name_utf8, GError **error_r); - -bool -spl_remove_index(const char *utf8path, unsigned pos, GError **error_r); - -bool -spl_append_song(const char *utf8path, struct song *song, GError **error_r); - -bool -spl_append_uri(const char *file, const char *utf8file, GError **error_r); - -bool -spl_rename(const char *utf8from, const char *utf8to, GError **error_r); - -#endif diff --git a/src/string_util.c b/src/string_util.c deleted file mode 100644 index 6e5429076..000000000 --- a/src/string_util.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "string_util.h" - -#include <glib.h> - -#include <assert.h> - -const char * -strchug_fast_c(const char *p) -{ - while (*p != 0 && g_ascii_isspace(*p)) - ++p; - - return p; -} - -bool -string_array_contains(const char *const* haystack, const char *needle) -{ - assert(haystack != NULL); - assert(needle != NULL); - - for (; *haystack != NULL; ++haystack) - if (g_ascii_strcasecmp(*haystack, needle) == 0) - return true; - - return false; -} diff --git a/src/string_util.h b/src/string_util.h deleted file mode 100644 index dc80a46ef..000000000 --- a/src/string_util.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STRING_UTIL_H -#define MPD_STRING_UTIL_H - -#include <glib.h> - -#include <stdbool.h> - -/** - * Remove the "const" attribute from a string pointer. This is a - * dirty hack, don't use it unless you know what you're doing! - */ -G_GNUC_CONST -static inline char * -deconst_string(const char *p) -{ - union { - const char *in; - char *out; - } u = { - .in = p, - }; - - return u.out; -} - -/** - * Returns a pointer to the first non-whitespace character in the - * string, or to the end of the string. - * - * This is a faster version of g_strchug(), because it does not move - * data. - */ -G_GNUC_PURE -const char * -strchug_fast_c(const char *p); - -/** - * Same as strchug_fast_c(), but works with a writable pointer. - */ -G_GNUC_PURE -static inline char * -strchug_fast(char *p) -{ - return deconst_string(strchug_fast_c(p)); -} - -/** - * Checks whether a string array contains the specified string. - * - * @param haystack a NULL terminated list of strings - * @param needle the string to search for; the comparison is - * case-insensitive for ASCII characters - * @return true if found - */ -bool -string_array_contains(const char *const* haystack, const char *needle); - -#endif diff --git a/src/strset.c b/src/strset.c deleted file mode 100644 index 5862e4075..000000000 --- a/src/strset.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "strset.h" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -#define NUM_SLOTS 16384 - -struct strset_slot { - struct strset_slot *next; - const char *value; -}; - -struct strset { - unsigned size; - - struct strset_slot *current_slot; - unsigned next_slot; - - struct strset_slot slots[NUM_SLOTS]; -}; - -static unsigned calc_hash(const char *p) { - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash; -} - -G_GNUC_MALLOC struct strset *strset_new(void) -{ - struct strset *set = g_new0(struct strset, 1); - return set; -} - -void strset_free(struct strset *set) -{ - unsigned i; - - for (i = 0; i < NUM_SLOTS; ++i) { - struct strset_slot *slot = set->slots[i].next, *next; - - while (slot != NULL) { - next = slot->next; - g_free(slot); - slot = next; - } - } - - g_free(set); -} - -void strset_add(struct strset *set, const char *value) -{ - struct strset_slot *base_slot - = &set->slots[calc_hash(value) % NUM_SLOTS]; - struct strset_slot *slot = base_slot; - - if (base_slot->value == NULL) { - /* empty slot - put into base_slot */ - assert(base_slot->next == NULL); - - base_slot->value = value; - ++set->size; - return; - } - - for (slot = base_slot; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return; - - /* insert it into the slot chain */ - slot = g_new(struct strset_slot, 1); - slot->next = base_slot->next; - slot->value = value; - base_slot->next = slot; - ++set->size; -} - -int strset_get(const struct strset *set, const char *value) -{ - const struct strset_slot *slot = &set->slots[calc_hash(value)]; - - if (slot->value == NULL) - return 0; - - for (slot = slot->next; slot != NULL; slot = slot->next) - if (strcmp(slot->value, value) == 0) - /* found it - do nothing */ - return 1; - - return 0; -} - -unsigned strset_size(const struct strset *set) -{ - return set->size; -} - -void strset_rewind(struct strset *set) -{ - set->current_slot = NULL; - set->next_slot = 0; -} - -const char *strset_next(struct strset *set) -{ - if (set->current_slot != NULL && set->current_slot->next != NULL) { - set->current_slot = set->current_slot->next; - return set->current_slot->value; - } - - while (set->next_slot < NUM_SLOTS && - set->slots[set->next_slot].value == NULL) - ++set->next_slot; - - if (set->next_slot >= NUM_SLOTS) - return NULL; - - set->current_slot = &set->slots[set->next_slot++]; - return set->current_slot->value; -} - diff --git a/src/strset.h b/src/strset.h deleted file mode 100644 index 5382e59b8..000000000 --- a/src/strset.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * "struct strset" is a hashed string set: you can add strings to this - * library, and it stores them as a set of unique strings. You can - * get the size of the set, and you can enumerate through all values. - * - * It is important to note that the strset does not copy the string - * values - it stores the exact pointers it was given in strset_add(). - */ - -#ifndef MPD_STRSET_H -#define MPD_STRSET_H - -#include <glib.h> - -struct strset; - -G_GNUC_MALLOC struct strset *strset_new(void); - -void strset_free(struct strset *set); - -void strset_add(struct strset *set, const char *value); - -int strset_get(const struct strset *set, const char *value); - -unsigned strset_size(const struct strset *set); - -void strset_rewind(struct strset *set); - -const char *strset_next(struct strset *set); - -#endif diff --git a/src/system/EPollFD.cxx b/src/system/EPollFD.cxx new file mode 100644 index 000000000..5721c0194 --- /dev/null +++ b/src/system/EPollFD.cxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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" +#ifdef USE_EPOLL +#include "EPollFD.hxx" +#include "FatalError.hxx" + +EPollFD::EPollFD() + :fd(::epoll_create1(EPOLL_CLOEXEC)) +{ + if (fd < 0) + FatalSystemError("epoll_create1() failed"); +} + +#endif /* USE_EPOLL */ diff --git a/src/system/EPollFD.hxx b/src/system/EPollFD.hxx new file mode 100644 index 000000000..41f7ec377 --- /dev/null +++ b/src/system/EPollFD.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EPOLL_FD_HXX +#define MPD_EPOLL_FD_HXX + +#include <assert.h> +#include <sys/epoll.h> +#include <unistd.h> + +#include "check.h" + +struct epoll_event; + +/** + * A class that wraps Linux epoll. + * + * Errors in the constructor are fatal. + */ +class EPollFD { + const int fd; + +public: + EPollFD(); + + ~EPollFD() { + assert(fd >= 0); + + ::close(fd); + } + + EPollFD(const EPollFD &other) = delete; + EPollFD &operator=(const EPollFD &other) = delete; + + int Wait(epoll_event *events, int maxevents, int timeout) { + return ::epoll_wait(fd, events, maxevents, timeout); + } + + bool Control(int op, int _fd, epoll_event *event) { + return ::epoll_ctl(fd, op, _fd, event) >= 0; + } + + bool Add(int _fd, uint32_t events, void *ptr) { + epoll_event e; + e.events = events; + e.data.ptr = ptr; + + return Control(EPOLL_CTL_ADD, _fd, &e); + } + + bool Modify(int _fd, uint32_t events, void *ptr) { + epoll_event e; + e.events = events; + e.data.ptr = ptr; + + return Control(EPOLL_CTL_MOD, _fd, &e); + } + + bool Remove(int _fd) { + return Control(EPOLL_CTL_DEL, _fd, nullptr); + } +}; + +#endif diff --git a/src/system/EventFD.cxx b/src/system/EventFD.cxx new file mode 100644 index 000000000..716ff80ae --- /dev/null +++ b/src/system/EventFD.cxx @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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" +#ifdef USE_EVENTFD +#include "EventFD.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "gcc.h" + +#include <assert.h> +#include <unistd.h> + +#include <sys/eventfd.h> + +EventFD::EventFD() + :fd(eventfd_cloexec_nonblock(0, 0)) +{ + if (fd < 0) + FatalSystemError("eventfd() failed"); +} + +EventFD::~EventFD() +{ + assert(fd >= 0); + + close(fd); +} + +bool +EventFD::Read() +{ + assert(fd >= 0); + + eventfd_t value; + return read(fd, &value, sizeof(value)) == (ssize_t)sizeof(value); +} + +void +EventFD::Write() +{ + assert(fd >= 0); + + static constexpr eventfd_t value = 1; + gcc_unused ssize_t nbytes = + write(fd, &value, sizeof(value)); +} + +#endif /* USE_EVENTFD */ diff --git a/src/system/EventFD.hxx b/src/system/EventFD.hxx new file mode 100644 index 000000000..67a0258ab --- /dev/null +++ b/src/system/EventFD.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EVENT_FD_HXX +#define MPD_EVENT_FD_HXX + +#include "check.h" + +/** + * A class that wraps eventfd(). + * + * Errors in the constructor are fatal. + */ +class EventFD { + int fd; + +public: + EventFD(); + ~EventFD(); + + EventFD(const EventFD &other) = delete; + EventFD &operator=(const EventFD &other) = delete; + + int Get() const { + return fd; + } + + /** + * Checks if Write() was called at least once since the last + * Read() call. + */ + bool Read(); + + /** + * Wakes up the reader. Multiple calls to this function will + * be combined to one wakeup. + */ + void Write(); +}; + +#endif diff --git a/src/system/EventPipe.cxx b/src/system/EventPipe.cxx new file mode 100644 index 000000000..e1a38c503 --- /dev/null +++ b/src/system/EventPipe.cxx @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "EventPipe.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "gcc.h" + +#include <assert.h> +#include <unistd.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock2.h> +#include <cstring> /* for memset() */ +#endif + +#ifdef WIN32 +static bool PoorSocketPair(int fd[2]); +#endif + +EventPipe::EventPipe() +{ +#ifdef WIN32 + bool success = PoorSocketPair(fds); +#else + bool success = pipe_cloexec_nonblock(fds) >= 0; +#endif + if (!success) + FatalSystemError("pipe() has failed"); +} + +EventPipe::~EventPipe() +{ +#ifdef WIN32 + closesocket(fds[0]); + closesocket(fds[1]); +#else + close(fds[0]); + close(fds[1]); +#endif +} + +bool +EventPipe::Read() +{ + assert(fds[0] >= 0); + assert(fds[1] >= 0); + + char buffer[256]; +#ifdef WIN32 + return recv(fds[0], buffer, sizeof(buffer), 0) > 0; +#else + return read(fds[0], buffer, sizeof(buffer)) > 0; +#endif +} + +void +EventPipe::Write() +{ + assert(fds[0] >= 0); + assert(fds[1] >= 0); + +#ifdef WIN32 + send(fds[1], "", 1, 0); +#else + gcc_unused ssize_t nbytes = write(fds[1], "", 1); +#endif +} + +#ifdef WIN32 + +static void SafeCloseSocket(SOCKET s) +{ + int error = WSAGetLastError(); + closesocket(s); + WSASetLastError(error); +} + +/* Our poor man's socketpair() implementation + * Due to limited protocol/address family support and primitive error handling + * it's better to keep this as a private implementation detail of EventPipe + * rather than wide-available API. + */ +static bool PoorSocketPair(int fd[2]) +{ + assert (fd != nullptr); + + SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_socket == INVALID_SOCKET) + return false; + + sockaddr_in address; + std::memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + int ret = bind(listen_socket, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = listen(listen_socket, 1); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + int address_len = sizeof(address); + ret = getsockname(listen_socket, + reinterpret_cast<sockaddr*>(&address), + &address_len); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + return false; + } + + SOCKET socket0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket0 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + return false; + } + + ret = connect(socket0, + reinterpret_cast<sockaddr*>(&address), + sizeof(address)); + + if (ret < 0) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SOCKET socket1 = accept(listen_socket, nullptr, nullptr); + if (socket1 == INVALID_SOCKET) { + SafeCloseSocket(listen_socket); + SafeCloseSocket(socket0); + return false; + } + + SafeCloseSocket(listen_socket); + + u_long non_block = 1; + if (ioctlsocket(socket0, FIONBIO, &non_block) < 0 + || ioctlsocket(socket1, FIONBIO, &non_block) < 0) { + SafeCloseSocket(socket0); + SafeCloseSocket(socket1); + return false; + } + + fd[0] = static_cast<int>(socket0); + fd[1] = static_cast<int>(socket1); + + return true; +} + +#endif diff --git a/src/system/EventPipe.hxx b/src/system/EventPipe.hxx new file mode 100644 index 000000000..86a10b0bb --- /dev/null +++ b/src/system/EventPipe.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EVENT_PIPE_HXX +#define MPD_EVENT_PIPE_HXX + +#include "check.h" + +/** + * A pipe that can be used to trigger an event to the read side. + * + * Errors in the constructor are fatal. + */ +class EventPipe { + int fds[2]; + +public: + EventPipe(); + ~EventPipe(); + + EventPipe(const EventPipe &other) = delete; + EventPipe &operator=(const EventPipe &other) = delete; + + int Get() const { + return fds[0]; + } + + /** + * Checks if Write() was called at least once since the last + * Read() call. + */ + bool Read(); + + /** + * Wakes up the reader. Multiple calls to this function will + * be combined to one wakeup. + */ + void Write(); +}; + +#endif /* MAIN_NOTIFY_H */ diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx new file mode 100644 index 000000000..1cf4ec851 --- /dev/null +++ b/src/system/FatalError.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "FatalError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "LogV.hxx" + +#include <glib.h> + +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> + +#ifdef WIN32 +#include <windows.h> +#else +#include <errno.h> +#endif + +static constexpr Domain fatal_error_domain("fatal_error"); + +void +FatalError(const char *msg) +{ + LogError(fatal_error_domain, msg); + exit(EXIT_FAILURE); +} + +void +FormatFatalError(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + LogFormatV(fatal_error_domain, LogLevel::ERROR, fmt, ap); + va_end(ap); + + exit(EXIT_FAILURE); +} + +void +FatalError(const Error &error) +{ + FatalError(error.GetMessage()); +} + +void +FatalError(const char *msg, const Error &error) +{ + FormatFatalError("%s: %s", msg, error.GetMessage()); +} + +void +FatalError(GError *error) +{ + FatalError(error->message); +} + +void +FatalError(const char *msg, GError *error) +{ + FormatFatalError("%s: %s", msg, error->message); +} + +void +FatalSystemError(const char *msg) +{ + const char *system_error; +#ifdef WIN32 + system_error = g_win32_error_message(GetLastError()); +#else + system_error = g_strerror(errno); +#endif + + FormatError(fatal_error_domain, "%s: %s", msg, system_error); + exit(EXIT_FAILURE); +} + +void +FormatFatalSystemError(const char *fmt, ...) +{ + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + FatalSystemError(buffer); +} diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx new file mode 100644 index 000000000..03baf66c4 --- /dev/null +++ b/src/system/FatalError.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FATAL_ERROR_HXX +#define MPD_FATAL_ERROR_HXX + +#include "check.h" +#include "gerror.h" +#include "gcc.h" + +class Error; + +/** + * Log the specified message and abort the process. + */ +gcc_noreturn +void +FatalError(const char *msg); + +gcc_noreturn +void +FormatFatalError(const char *fmt, ...); + +gcc_noreturn +void +FatalError(const Error &error); + +gcc_noreturn +void +FatalError(const char *msg, const Error &error); + +gcc_noreturn +void +FatalError(GError *error); + +gcc_noreturn +void +FatalError(const char *msg, GError *error); + +/** + * Call this after a system call has failed that is not supposed to + * fail. Prints the given message, the system error message (from + * errno or GetLastError()) and abort the process. + */ +gcc_noreturn +void +FatalSystemError(const char *msg); + +gcc_noreturn +void +FormatFatalSystemError(const char *fmt, ...); + +#endif diff --git a/src/system/Resolver.cxx b/src/system/Resolver.cxx new file mode 100644 index 000000000..51a5b1ed7 --- /dev/null +++ b/src/system/Resolver.cxx @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Resolver.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#ifndef G_OS_WIN32 +#include <sys/socket.h> +#include <netdb.h> +#else /* G_OS_WIN32 */ +#include <ws2tcpip.h> +#include <winsock.h> +#endif /* G_OS_WIN32 */ + +#include <string.h> +#include <stdio.h> + +const Domain resolver_domain("resolver"); + +char * +sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error) +{ +#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) + const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)sa; + struct sockaddr_in a4; +#endif + int ret; + char host[NI_MAXHOST], serv[NI_MAXSERV]; + +#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) + if (sa->sa_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) { + /* convert "::ffff:127.0.0.1" to "127.0.0.1" */ + + memset(&a4, 0, sizeof(a4)); + a4.sin_family = AF_INET; + memcpy(&a4.sin_addr, ((const char *)&a6->sin6_addr) + 12, + sizeof(a4.sin_addr)); + a4.sin_port = a6->sin6_port; + + sa = (const struct sockaddr *)&a4; + length = sizeof(a4); + } +#endif + + ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ret != 0) { + error.Set(resolver_domain, ret, gai_strerror(ret)); + return NULL; + } + +#ifdef HAVE_UN + if (sa->sa_family == AF_UNIX) + /* "serv" contains corrupt information with unix + sockets */ + return g_strdup(host); +#endif + +#ifdef HAVE_IPV6 + if (strchr(host, ':') != NULL) + return g_strconcat("[", host, "]:", serv, NULL); +#endif + + return g_strconcat(host, ":", serv, NULL); +} + +struct addrinfo * +resolve_host_port(const char *host_port, unsigned default_port, + int flags, int socktype, + Error &error) +{ + char *p = g_strdup(host_port); + const char *host = p, *port = NULL; + + if (host_port[0] == '[') { + /* IPv6 needs enclosing square braces, to + differentiate between IP colons and the port + separator */ + + char *q = strchr(p + 1, ']'); + if (q != NULL && q[1] == ':' && q[2] != 0) { + *q = 0; + ++host; + port = q + 2; + } + } + + if (port == NULL) { + /* port is after the colon, but only if it's the only + colon (don't split IPv6 addresses) */ + + char *q = strchr(p, ':'); + if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) { + *q = 0; + port = q + 1; + } + } + + char buffer[32]; + if (port == NULL && default_port != 0) { + snprintf(buffer, sizeof(buffer), "%u", default_port); + port = buffer; + } + + if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0) + host = NULL; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = flags; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = socktype; + + struct addrinfo *ai; + int ret = getaddrinfo(host, port, &hints, &ai); + g_free(p); + if (ret != 0) { + error.Format(resolver_domain, ret, + "Failed to look up '%s': %s", + host_port, gai_strerror(ret)); + return NULL; + } + + return ai; +} diff --git a/src/system/Resolver.hxx b/src/system/Resolver.hxx new file mode 100644 index 000000000..38803dcd1 --- /dev/null +++ b/src/system/Resolver.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_RESOLVER_HXX +#define MPD_RESOLVER_HXX + +#include "gcc.h" + +#include <stddef.h> + +struct sockaddr; +struct addrinfo; +class Error; + +extern const class Domain resolver_domain; + +/** + * Converts the specified socket address into a string in the form + * "IP:PORT". The return value must be freed with g_free() when you + * don't need it anymore. + * + * @param sa the sockaddr struct + * @param length the length of #sa in bytes + * @param error location to store the error occurring, or NULL to + * ignore errors + */ +gcc_malloc +char * +sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error); + +/** + * Resolve a specification in the form "host", "host:port", + * "[host]:port". This is a convenience wrapper for getaddrinfo(). + * + * @param default_port a default port number that will be used if none + * is given in the string (if applicable); pass 0 to go without a + * default + * @return an #addrinfo linked list that must be freed with + * freeaddrinfo(), or NULL on error + */ +struct addrinfo * +resolve_host_port(const char *host_port, unsigned default_port, + int flags, int socktype, + Error &error); + +#endif diff --git a/src/system/SignalFD.cxx b/src/system/SignalFD.cxx new file mode 100644 index 000000000..b89775dcd --- /dev/null +++ b/src/system/SignalFD.cxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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" +#ifdef USE_SIGNALFD +#include "SignalFD.hxx" +#include "fd_util.h" +#include "FatalError.hxx" + +#include <assert.h> +#include <unistd.h> +#include <sys/signalfd.h> + +void +SignalFD::Create(const sigset_t &mask) +{ + fd = ::signalfd(fd, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (fd < 0) + FatalSystemError("signalfd() failed"); +} + +void +SignalFD::Close() +{ + if (fd >= 0) { + ::close(fd); + fd = -1; + } +} + +int +SignalFD::Read() +{ + assert(fd >= 0); + + signalfd_siginfo info; + return read(fd, &info, sizeof(info)) > 0 + ? info.ssi_signo + : -1; +} + +#endif /* USE_SIGNALFD */ diff --git a/src/system/SignalFD.hxx b/src/system/SignalFD.hxx new file mode 100644 index 000000000..7163782d1 --- /dev/null +++ b/src/system/SignalFD.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SIGNAL_FD_HXX +#define MPD_SIGNAL_FD_HXX + +#include "check.h" + +#include <signal.h> + +/** + * A class that wraps signalfd(). + */ +class SignalFD { + int fd; + +public: + SignalFD():fd(-1) {} + ~SignalFD() { + Close(); + } + + SignalFD(const SignalFD &other) = delete; + SignalFD &operator=(const SignalFD &other) = delete; + + /** + * Create the signalfd or update its mask. + * + * All errors are fatal. + */ + void Create(const sigset_t &mask); + void Close(); + + int Get() const { + return fd; + } + + /** + * Read the next signal from the file descriptor. Returns the + * signal number on success or -1 if there are no more + * signals. + */ + int Read(); +}; + +#endif diff --git a/src/system/SocketError.cxx b/src/system/SocketError.cxx new file mode 100644 index 000000000..315a86e1f --- /dev/null +++ b/src/system/SocketError.cxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SocketError.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +const Domain socket_domain("socket"); + +#ifdef WIN32 + +SocketErrorMessage::SocketErrorMessage(socket_error_t code) +{ + DWORD nbytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, code, 0, + (LPSTR)msg, sizeof(msg), NULL); + if (nbytes == 0) + strcpy(msg, "Unknown error"); +} + +#else + +SocketErrorMessage::SocketErrorMessage(socket_error_t code) + :msg(g_strerror(code)) {} + +#endif diff --git a/src/system/SocketError.hxx b/src/system/SocketError.hxx new file mode 100644 index 000000000..2b8332715 --- /dev/null +++ b/src/system/SocketError.hxx @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOCKET_ERROR_HXX +#define MPD_SOCKET_ERROR_HXX + +#include "gcc.h" +#include "util/Error.hxx" + +#ifdef WIN32 +#include <winsock2.h> +typedef DWORD socket_error_t; +#else +#include <errno.h> +typedef int socket_error_t; +#endif + +/** + * A #Domain for #Error for socket I/O errors. The code is an errno + * value (or WSAGetLastError() on Windows). + */ +extern const class Domain socket_domain; + +gcc_pure +static inline socket_error_t +GetSocketError() +{ +#ifdef WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +gcc_const +static inline bool +IsSocketErrorAgain(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINPROGRESS; +#else + return code == EAGAIN; +#endif +} + +gcc_const +static inline bool +IsSocketErrorInterruped(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAEINTR; +#else + return code == EINTR; +#endif +} + +gcc_const +static inline bool +IsSocketErrorClosed(socket_error_t code) +{ +#ifdef WIN32 + return code == WSAECONNRESET; +#else + return code == EPIPE || code == ECONNRESET; +#endif +} + +/** + * Helper class that formats a socket error message into a + * human-readable string. On Windows, a buffer is necessary for this, + * and this class hosts the buffer. + */ +class SocketErrorMessage { +#ifdef WIN32 + char msg[256]; +#else + const char *const msg; +#endif + +public: +#ifdef WIN32 + explicit SocketErrorMessage(socket_error_t code=GetSocketError()); +#else + explicit SocketErrorMessage(socket_error_t code=GetSocketError()); +#endif + + operator const char *() const { + return msg; + } +}; + +static inline void +SetSocketError(Error &error, socket_error_t code) +{ + const SocketErrorMessage msg(code); + error.Set(socket_domain, code, msg); +} + +static inline void +SetSocketError(Error &error) +{ + SetSocketError(error, GetSocketError()); +} + +gcc_const +static inline Error +NewSocketError(socket_error_t code) +{ + Error error; + SetSocketError(error, code); + return error; +} + +gcc_pure +static inline Error +NewSocketError() +{ + return NewSocketError(GetSocketError()); +} + +#endif diff --git a/src/system/SocketUtil.cxx b/src/system/SocketUtil.cxx new file mode 100644 index 000000000..abaedb8e7 --- /dev/null +++ b/src/system/SocketUtil.cxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "SocketUtil.hxx" +#include "SocketError.hxx" +#include "fd_util.h" + +#include <glib.h> + +#include <unistd.h> + +#ifndef G_OS_WIN32 +#include <sys/socket.h> +#else /* G_OS_WIN32 */ +#include <ws2tcpip.h> +#include <winsock.h> +#endif /* G_OS_WIN32 */ + +#ifdef HAVE_IPV6 +#include <string.h> +#endif + +int +socket_bind_listen(int domain, int type, int protocol, + const struct sockaddr *address, size_t address_length, + int backlog, + Error &error) +{ + int fd, ret; + const int reuse = 1; + + fd = socket_cloexec_nonblock(domain, type, protocol); + if (fd < 0) { + SetSocketError(error); + error.AddPrefix("Failed to create socket: "); + return -1; + } + + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (const char *) &reuse, sizeof(reuse)); + if (ret < 0) { + SetSocketError(error); + error.AddPrefix("setsockopt() failed: "); + close_socket(fd); + return -1; + } + + ret = bind(fd, address, address_length); + if (ret < 0) { + SetSocketError(error); + close_socket(fd); + return -1; + } + + ret = listen(fd, backlog); + if (ret < 0) { + SetSocketError(error); + error.AddPrefix("listen() failed: "); + close_socket(fd); + return -1; + } + +#ifdef HAVE_STRUCT_UCRED + setsockopt(fd, SOL_SOCKET, SO_PASSCRED, + (const char *) &reuse, sizeof(reuse)); +#endif + + return fd; +} + +int +socket_keepalive(int fd) +{ + const int reuse = 1; + + return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (const char *)&reuse, sizeof(reuse)); +} diff --git a/src/system/SocketUtil.hxx b/src/system/SocketUtil.hxx new file mode 100644 index 000000000..5e582ec0d --- /dev/null +++ b/src/system/SocketUtil.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This library provides easy helper functions for working with + * sockets. + * + */ + +#ifndef MPD_SOCKET_UTIL_HXX +#define MPD_SOCKET_UTIL_HXX + +#include <stddef.h> + +struct sockaddr; +class Error; + +/** + * Creates a socket listening on the specified address. This is a + * shortcut for socket(), bind() and listen(). + * + * @param domain the socket domain, e.g. PF_INET6 + * @param type the socket type, e.g. SOCK_STREAM + * @param protocol the protocol, usually 0 to let the kernel choose + * @param address the address to listen on + * @param address_length the size of #address + * @param backlog the backlog parameter for the listen() system call + * @param error location to store the error occurring, or NULL to + * ignore errors + * @return the socket file descriptor or -1 on error + */ +int +socket_bind_listen(int domain, int type, int protocol, + const struct sockaddr *address, size_t address_length, + int backlog, + Error &error); + +int +socket_keepalive(int fd); + +#endif diff --git a/src/clock.c b/src/system/clock.c index d987aed48..d987aed48 100644 --- a/src/clock.c +++ b/src/system/clock.c diff --git a/src/system/clock.h b/src/system/clock.h new file mode 100644 index 000000000..c98b9a652 --- /dev/null +++ b/src/system/clock.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLOCK_H +#define MPD_CLOCK_H + +#include "gcc.h" + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns the value of a monotonic clock in milliseconds. + */ +gcc_pure +unsigned +monotonic_clock_ms(void); + +/** + * Returns the value of a monotonic clock in microseconds. + */ +gcc_pure +uint64_t +monotonic_clock_us(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/system/fd_util.c b/src/system/fd_util.c new file mode 100644 index 000000000..17976a5ee --- /dev/null +++ b/src/system/fd_util.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2003-2011 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 + +#ifdef USE_EVENTFD +#include <sys/eventfd.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; +} + +int +socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]) +{ + int ret; + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + ret = socketpair(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol, + sv); + if (ret >= 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret >= 0) { + fd_set_cloexec(sv[0], true); + fd_set_nonblock(sv[0]); + fd_set_cloexec(sv[1], true); + fd_set_nonblock(sv[1]); + } + + return ret; +} + +#endif + +int +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 + +#ifdef USE_EVENTFD + +int +eventfd_cloexec_nonblock(unsigned initval, int flags) +{ + return eventfd(initval, flags | EFD_CLOEXEC | EFD_NONBLOCK); +} + +#endif + +int +close_socket(int fd) +{ +#ifdef WIN32 + return closesocket(fd); +#else + return close(fd); +#endif +} diff --git a/src/system/fd_util.h b/src/system/fd_util.h new file mode 100644 index 000000000..9003b1616 --- /dev/null +++ b/src/system/fd_util.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003-2011 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 "check.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; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * 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]); + +/** + * Wrapper for socketpair(), which sets the flags CLOEXEC and NONBLOCK + * (atomically if supported by the OS). + */ +int +socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]); + +#endif + +/** + * 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 + +#ifdef HAVE_INOTIFY_INIT + +/** + * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically + * if supported by the OS). + */ +int +inotify_init_cloexec(void); + +#endif + +#ifdef USE_EVENTFD + +/** + * Wrapper for eventfd() which sets the flags CLOEXEC and NONBLOCK + * flag (atomically if supported by the OS). + */ +int +eventfd_cloexec_nonblock(unsigned initval, int flags); + +#endif + +/** + * Portable wrapper for close(); use closesocket() on WIN32/WinSock. + */ +int +close_socket(int fd); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/tag.c b/src/tag.c deleted file mode 100644 index c0faa7ab2..000000000 --- a/src/tag.c +++ /dev/null @@ -1,523 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag.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> -#include <stdio.h> -#include <stdlib.h> - -/** - * Maximum number of items managed in the bulk list; if it is - * exceeded, we switch back to "normal" reallocation. - */ -#define BULK_MAX 64 - -static struct { -#ifndef NDEBUG - bool busy; -#endif - struct tag_item *items[BULK_MAX]; -} bulk; - -const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - [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", - [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", - [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", - [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", -}; - -bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; - -enum tag_type -tag_name_parse(const char *name) -{ - assert(name != NULL); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); - - if (strcmp(name, tag_item_names[i]) == 0) - return (enum tag_type)i; - } - - return TAG_NUM_OF_ITEM_TYPES; -} - -enum tag_type -tag_name_parse_i(const char *name) -{ - assert(name != NULL); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - assert(tag_item_names[i] != NULL); - - if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) - return (enum tag_type)i; - } - - return TAG_NUM_OF_ITEM_TYPES; -} - -static size_t items_size(const struct tag *tag) -{ - return tag->num_items * sizeof(struct tag_item *); -} - -void tag_lib_init(void) -{ - const char *value; - int quit = 0; - char *temp; - char *s; - char *c; - enum tag_type type; - - /* parse the "metadata_to_use" config parameter below */ - - /* ignore comments by default */ - ignore_tag_items[TAG_COMMENT] = true; - - value = config_get_string(CONF_METADATA_TO_USE, NULL); - if (value == NULL) - return; - - memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES); - - if (0 == g_ascii_strcasecmp(value, "none")) - return; - - temp = c = s = g_strdup(value); - while (!quit) { - if (*s == ',' || *s == '\0') { - if (*s == '\0') - quit = 1; - *s = '\0'; - - c = g_strstrip(c); - if (*c == 0) - continue; - - type = tag_name_parse_i(c); - if (type == TAG_NUM_OF_ITEM_TYPES) - MPD_ERROR("error parsing metadata item \"%s\"", - c); - - ignore_tag_items[type] = false; - - s++; - c = s; - } - s++; - } - - g_free(temp); -} - -struct tag *tag_new(void) -{ - struct tag *ret = g_new(struct tag, 1); - ret->items = NULL; - ret->time = -1; - ret->has_playlist = false; - ret->num_items = 0; - return ret; -} - -static void tag_delete_item(struct tag *tag, unsigned idx) -{ - assert(idx < tag->num_items); - tag->num_items--; - - g_mutex_lock(tag_pool_lock); - tag_pool_put_item(tag->items[idx]); - g_mutex_unlock(tag_pool_lock); - - if (tag->num_items - idx > 0) { - memmove(tag->items + idx, tag->items + idx + 1, - (tag->num_items - idx) * sizeof(tag->items[0])); - } - - if (tag->num_items > 0) { - tag->items = g_realloc(tag->items, items_size(tag)); - } else { - g_free(tag->items); - tag->items = NULL; - } -} - -void tag_clear_items_by_type(struct tag *tag, enum tag_type type) -{ - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == type) { - tag_delete_item(tag, i); - /* decrement since when just deleted this node */ - i--; - } - } -} - -void tag_free(struct tag *tag) -{ - int i; - - assert(tag != NULL); - - g_mutex_lock(tag_pool_lock); - for (i = tag->num_items; --i >= 0; ) - tag_pool_put_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); - - if (tag->items == bulk.items) { -#ifndef NDEBUG - assert(bulk.busy); - bulk.busy = false; -#endif - } else - g_free(tag->items); - - g_free(tag); -} - -struct tag *tag_dup(const struct tag *tag) -{ - struct tag *ret; - - if (!tag) - return NULL; - - ret = tag_new(); - ret->time = tag->time; - ret->has_playlist = tag->has_playlist; - ret->num_items = tag->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; - - g_mutex_lock(tag_pool_lock); - for (unsigned i = 0; i < tag->num_items; i++) - ret->items[i] = tag_pool_dup_item(tag->items[i]); - g_mutex_unlock(tag_pool_lock); - - return ret; -} - -struct tag * -tag_merge(const struct tag *base, const struct tag *add) -{ - struct tag *ret; - unsigned n; - - assert(base != NULL); - assert(add != NULL); - - /* allocate new tag object */ - - ret = tag_new(); - ret->time = add->time > 0 ? add->time : base->time; - ret->num_items = base->num_items + add->num_items; - ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL; - - g_mutex_lock(tag_pool_lock); - - /* copy all items from "add" */ - - for (unsigned i = 0; i < add->num_items; ++i) - ret->items[i] = tag_pool_dup_item(add->items[i]); - - n = add->num_items; - - /* copy additional items from "base" */ - - for (unsigned i = 0; i < base->num_items; ++i) - if (!tag_has_type(add, base->items[i]->type)) - ret->items[n++] = tag_pool_dup_item(base->items[i]); - - g_mutex_unlock(tag_pool_lock); - - assert(n <= ret->num_items); - - if (n < ret->num_items) { - /* some tags were not copied - shrink ret->items */ - assert(n > 0); - - ret->num_items = n; - ret->items = g_realloc(ret->items, items_size(ret)); - } - - return ret; -} - -struct tag * -tag_merge_replace(struct tag *base, struct tag *add) -{ - if (add == NULL) - return base; - - if (base == NULL) - return add; - - struct tag *tag = tag_merge(base, add); - tag_free(base); - tag_free(add); - - return tag; -} - -const char * -tag_get_value(const struct tag *tag, enum tag_type type) -{ - assert(tag != NULL); - assert(type < TAG_NUM_OF_ITEM_TYPES); - - for (unsigned i = 0; i < tag->num_items; i++) - if (tag->items[i]->type == type) - return tag->items[i]->value; - - return NULL; -} - -bool tag_has_type(const struct tag *tag, enum tag_type type) -{ - return tag_get_value(tag, type) != NULL; -} - -bool tag_equal(const struct tag *tag1, const struct tag *tag2) -{ - if (tag1 == NULL && tag2 == NULL) - return true; - else if (!tag1 || !tag2) - return false; - - if (tag1->time != tag2->time) - return false; - - if (tag1->num_items != tag2->num_items) - return false; - - for (unsigned i = 0; i < tag1->num_items; i++) { - if (tag1->items[i]->type != tag2->items[i]->type) - return false; - if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) { - return false; - } - } - - return true; -} - -/** - * Replace invalid sequences with the question mark. - */ -static char * -patch_utf8(const char *src, size_t length, const gchar *end) -{ - /* duplicate the string, and replace invalid bytes in that - buffer */ - char *dest = g_strdup(src); - - do { - dest[end - src] = '?'; - } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end)); - - return dest; -} - -static char * -fix_utf8(const char *str, size_t length) -{ - const gchar *end; - char *temp; - gsize written; - - assert(str != NULL); - - /* check if the string is already valid UTF-8 */ - if (g_utf8_validate(str, length, &end)) - return NULL; - - /* no, it's not - try to import it from ISO-Latin-1 */ - temp = g_convert(str, length, "utf-8", "iso-8859-1", - NULL, &written, NULL); - if (temp != NULL) - /* success! */ - return temp; - - /* no, still broken - there's no medication, just patch - invalid sequences */ - return patch_utf8(str, length, end); -} - -void tag_begin_add(struct tag *tag) -{ - assert(!bulk.busy); - assert(tag != NULL); - assert(tag->items == NULL); - assert(tag->num_items == 0); - -#ifndef NDEBUG - bulk.busy = true; -#endif - tag->items = bulk.items; -} - -void tag_end_add(struct tag *tag) -{ - if (tag->items == bulk.items) { - assert(tag->num_items <= BULK_MAX); - - if (tag->num_items > 0) { - /* copy the tag items from the bulk list over - to a new list (which fits exactly) */ - tag->items = g_malloc(items_size(tag)); - memcpy(tag->items, bulk.items, items_size(tag)); - } else - tag->items = NULL; - } - -#ifndef NDEBUG - bulk.busy = false; -#endif -} - -static bool -char_is_non_printable(unsigned char ch) -{ - return ch < 0x20; -} - -static const char * -find_non_printable(const char *p, size_t length) -{ - for (size_t i = 0; i < length; ++i) - if (char_is_non_printable(p[i])) - return p + i; - - return NULL; -} - -/** - * Clears all non-printable characters, convert them to space. - * Returns NULL if nothing needs to be cleared. - */ -static char * -clear_non_printable(const char *p, size_t length) -{ - const char *first = find_non_printable(p, length); - char *dest; - - if (first == NULL) - return NULL; - - dest = g_strndup(p, length); - - for (size_t i = first - p; i < length; ++i) - if (char_is_non_printable(dest[i])) - dest[i] = ' '; - - return dest; -} - -static char * -fix_tag_value(const char *p, size_t length) -{ - char *utf8, *cleared; - - utf8 = fix_utf8(p, length); - if (utf8 != NULL) { - p = utf8; - length = strlen(p); - } - - cleared = clear_non_printable(p, length); - if (cleared == NULL) - cleared = utf8; - else - g_free(utf8); - - return cleared; -} - -static void -tag_add_item_internal(struct tag *tag, enum tag_type type, - const char *value, size_t len) -{ - unsigned int i = tag->num_items; - char *p; - - p = fix_tag_value(value, len); - if (p != NULL) { - value = p; - len = strlen(value); - } - - tag->num_items++; - - if (tag->items != bulk.items) - /* bulk mode disabled */ - tag->items = g_realloc(tag->items, items_size(tag)); - else if (tag->num_items >= BULK_MAX) { - /* bulk list already full - switch back to non-bulk */ - assert(bulk.busy); - - tag->items = g_malloc(items_size(tag)); - memcpy(tag->items, bulk.items, - items_size(tag) - sizeof(struct tag_item *)); - } - - g_mutex_lock(tag_pool_lock); - tag->items[i] = tag_pool_get_item(type, value, len); - g_mutex_unlock(tag_pool_lock); - - g_free(p); -} - -void tag_add_item_n(struct tag *tag, enum tag_type type, - const char *value, size_t len) -{ - if (ignore_tag_items[type]) - { - return; - } - if (!value || !len) - return; - - tag_add_item_internal(tag, type, value, len); -} diff --git a/src/tag.h b/src/tag.h deleted file mode 100644 index 2556cf3a7..000000000 --- a/src/tag.h +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_H -#define MPD_TAG_H - -#include "gcc.h" - -#include <stdint.h> -#include <stddef.h> -#include <stdbool.h> -#include <string.h> - -/** - * Codes for the type of a tag item. - */ -enum tag_type { - 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, - TAG_MUSICBRAINZ_ALBUMARTISTID, - TAG_MUSICBRAINZ_TRACKID, - - TAG_NUM_OF_ITEM_TYPES -}; - -/** - * An array of strings, which map the #tag_type to its machine - * readable name (specific to the MPD protocol). - */ -extern const char *tag_item_names[]; - -/** - * One tag value. It is a mapping of #tag_type to am arbitrary string - * value. Each tag can have multiple items of one tag type (although - * few clients support that). - */ -struct tag_item { - /** the type of this item */ - enum tag_type type; - - /** - * the value of this tag; this is a variable length string - */ - char value[sizeof(long)]; -} gcc_packed; - -/** - * The meta information about a song file. It is a MPD specific - * subset of tags (e.g. from ID3, vorbis comments, ...). - */ -struct tag { - /** - * The duration of the song (in seconds). A value of zero - * means that the length is unknown. If the duration is - * really between zero and one second, you should round up to - * 1. - */ - int time; - - /** - * Does this file have an embedded playlist (e.g. embedded CUE - * sheet)? - */ - bool has_playlist; - - /** an array of tag items */ - struct tag_item **items; - - /** the total number of tag items in the #items array */ - unsigned num_items; -}; - -/** - * Parse the string, and convert it into a #tag_type. Returns - * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. - */ -enum tag_type -tag_name_parse(const char *name); - -/** - * Parse the string, and convert it into a #tag_type. Returns - * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. - * - * Case does not matter. - */ -enum tag_type -tag_name_parse_i(const char *name); - -/** - * Creates an empty #tag. - */ -struct tag *tag_new(void); - -/** - * Initializes the tag library. - */ -void tag_lib_init(void); - -/** - * Clear all tag items with the specified type. - */ -void tag_clear_items_by_type(struct tag *tag, enum tag_type type); - -/** - * Frees a #tag object and all its items. - */ -void tag_free(struct tag *tag); - -/** - * Gives an optional hint to the tag library that we will now add - * several tag items; this is used by the library to optimize memory - * allocation. Only one tag may be in this state, and this tag must - * not have any items yet. You must call tag_end_add() when you are - * done. - */ -void tag_begin_add(struct tag *tag); - -/** - * Finishes the operation started with tag_begin_add(). - */ -void tag_end_add(struct tag *tag); - -/** - * Appends a new tag item. - * - * @param tag the #tag object - * @param type the type of the new tag item - * @param value the value of the tag item (not null-terminated) - * @param len the length of #value - */ -void tag_add_item_n(struct tag *tag, enum tag_type type, - const char *value, size_t len); - -/** - * Appends a new tag item. - * - * @param tag the #tag object - * @param type the type of the new tag item - * @param value the value of the tag item (null-terminated) - */ -static inline void -tag_add_item(struct tag *tag, enum tag_type type, const char *value) -{ - tag_add_item_n(tag, type, value, strlen(value)); -} - -/** - * Duplicates a #tag object. - */ -struct tag *tag_dup(const struct tag *tag); - -/** - * Merges the data from two tags. If both tags share data for the - * same tag_type, only data from "add" is used. - * - * @return a newly allocated tag, which must be freed with tag_free() - */ -struct tag * -tag_merge(const struct tag *base, const struct tag *add); - -/** - * Merges the data from two tags. Any of the two may be NULL. Both - * are freed by this function. - * - * @return a newly allocated tag, which must be freed with tag_free() - */ -struct tag * -tag_merge_replace(struct tag *base, struct tag *add); - -/** - * Returns true if the tag contains no items. This ignores the "time" - * attribute. - */ -static inline bool -tag_is_empty(const struct tag *tag) -{ - return tag->num_items == 0; -} - -/** - * Returns true if the tag contains any information. - */ -static inline bool -tag_is_defined(const struct tag *tag) -{ - return !tag_is_empty(tag) || tag->time >= 0; -} - -/** - * Returns the first value of the specified tag type, or NULL if none - * is present in this tag object. - */ -const char * -tag_get_value(const struct tag *tag, enum tag_type type); - -/** - * Checks whether the tag contains one or more items with - * the specified type. - */ -bool tag_has_type(const struct tag *tag, enum tag_type type); - -/** - * Compares two tags, including the duration and all tag items. The - * order of the tag items matters. - */ -bool tag_equal(const struct tag *tag1, const struct tag *tag2); - -#endif diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx new file mode 100644 index 000000000..51622fec7 --- /dev/null +++ b/src/tag/Aiff.cxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Aiff.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> + +static constexpr Domain aiff_domain("aiff"); + +struct aiff_header { + char id[4]; + uint32_t size; + char format[4]; +}; + +struct aiff_chunk_header { + char id[4]; + uint32_t size; +}; + +size_t +aiff_seek_id3(FILE *file) +{ + /* determine the file size */ + + struct stat st; + if (fstat(fileno(file), &st) < 0) { + LogErrno(aiff_domain, "Failed to stat file descriptor"); + return 0; + } + + /* seek to the beginning and read the AIFF header */ + + if (fseek(file, 0, SEEK_SET) != 0) { + LogErrno(aiff_domain, "Failed to seek"); + return 0; + } + + aiff_header header; + size_t size = fread(&header, sizeof(header), 1, file); + if (size != 1 || + memcmp(header.id, "FORM", 4) != 0 || + GUINT32_FROM_BE(header.size) > (uint32_t)st.st_size || + (memcmp(header.format, "AIFF", 4) != 0 && + memcmp(header.format, "AIFC", 4) != 0)) + /* not a AIFF file */ + return 0; + + while (true) { + /* read the chunk header */ + + aiff_chunk_header chunk; + size = fread(&chunk, sizeof(chunk), 1, file); + if (size != 1) + return 0; + + size = GUINT32_FROM_BE(chunk.size); + if (size > G_MAXINT32) + /* too dangerous, bail out: possible integer + underflow when casting to off_t */ + return 0; + + if (size % 2 != 0) + /* pad byte */ + ++size; + + if (memcmp(chunk.id, "ID3 ", 4) == 0) + /* found it! */ + return size; + + if (fseek(file, size, SEEK_CUR) != 0) + return 0; + } +} diff --git a/src/tag/Aiff.hxx b/src/tag/Aiff.hxx new file mode 100644 index 000000000..9000be7f8 --- /dev/null +++ b/src/tag/Aiff.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 parser for the AIFF file format. + */ + +#ifndef MPD_AIFF_HXX +#define MPD_AIFF_HXX + +#include <stddef.h> +#include <stdio.h> + +/** + * Seeks the AIFF file to the ID3 chunk. + * + * @return the size of the ID3 chunk on success, or 0 if this is not a + * AIFF file or no ID3 chunk was found + */ +size_t +aiff_seek_id3(FILE *file); + +#endif diff --git a/src/tag/ApeLoader.cxx b/src/tag/ApeLoader.cxx new file mode 100644 index 000000000..dfbdb4ef3 --- /dev/null +++ b/src/tag/ApeLoader.cxx @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ApeLoader.hxx" + +#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, ApeTagCallback callback) +{ + /* 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 = (char *)g_malloc(remaining); + if (fread(buffer, 1, remaining, fp) != remaining) { + g_free(buffer); + return false; + } + + /* read tags */ + unsigned n = GUINT32_FROM_LE(footer.count); + const char *p = buffer; + while (n-- && remaining > 10) { + size_t size = GUINT32_FROM_LE(*(const uint32_t *)p); + p += 4; + remaining -= 4; + unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p); + p += 4; + remaining -= 4; + + /* get the key */ + const char *key = p; + while (remaining > size && *p != '\0') { + p++; + remaining--; + } + p++; + remaining--; + + /* get the value */ + if (remaining < size) + break; + + if (!callback(flags, key, p, size)) + break; + + p += size; + remaining -= size; + } + + g_free(buffer); + return true; +} + +bool +tag_ape_scan(const char *path_fs, ApeTagCallback callback) +{ + FILE *fp; + + fp = fopen(path_fs, "rb"); + if (fp == nullptr) + return false; + + bool success = ape_scan_internal(fp, callback); + fclose(fp); + return success; +} diff --git a/src/tag/ApeLoader.hxx b/src/tag/ApeLoader.hxx new file mode 100644 index 000000000..a32ab840c --- /dev/null +++ b/src/tag/ApeLoader.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LOADER_HXX +#define MPD_APE_LOADER_HXX + +#include "check.h" + +#include <functional> + +#include <stddef.h> + +typedef std::function<bool(unsigned long flags, const char *key, + const char *value, + size_t value_length)> ApeTagCallback; + +/** + * 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, ApeTagCallback callback); + +#endif diff --git a/src/tag/ApeReplayGain.cxx b/src/tag/ApeReplayGain.cxx new file mode 100644 index 000000000..817eca349 --- /dev/null +++ b/src/tag/ApeReplayGain.cxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ApeReplayGain.hxx" +#include "ApeLoader.hxx" +#include "ReplayGainInfo.hxx" + +#include <glib.h> + +#include <string.h> +#include <stdlib.h> + +static bool +replay_gain_ape_callback(unsigned long flags, const char *key, + const char *_value, size_t value_length, + struct replay_gain_info *info) +{ + /* we only care about utf-8 text tags */ + if ((flags & (0x3 << 1)) != 0) + return false; + + char value[16]; + if (value_length >= sizeof(value)) + return false; + + memcpy(value, _value, value_length); + value[value_length] = 0; + + if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) { + info->tuples[REPLAY_GAIN_TRACK].gain = atof(value); + return true; + } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) { + info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value); + return true; + } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) { + info->tuples[REPLAY_GAIN_TRACK].peak = atof(value); + return true; + } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) { + info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value); + return true; + } else + return false; +} + +bool +replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info) +{ + bool found = false; + + auto callback = [info, &found] + (unsigned long flags, const char *key, + const char *value, + size_t value_length) { + found |= replay_gain_ape_callback(flags, key, + value, value_length, + info); + return true; + }; + + return tag_ape_scan(path_fs, callback) && found; +} diff --git a/src/tag/ApeReplayGain.hxx b/src/tag/ApeReplayGain.hxx new file mode 100644 index 000000000..4bc34a9d2 --- /dev/null +++ b/src/tag/ApeReplayGain.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_REPLAY_GAIN_HXX +#define MPD_APE_REPLAY_GAIN_HXX + +#include "check.h" + +struct replay_gain_info; + +bool +replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info); + +#endif diff --git a/src/tag/ApeTag.cxx b/src/tag/ApeTag.cxx new file mode 100644 index 000000000..acaeada25 --- /dev/null +++ b/src/tag/ApeTag.cxx @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ApeTag.hxx" +#include "ApeLoader.hxx" +#include "Tag.hxx" +#include "TagTable.hxx" +#include "TagHandler.hxx" + +#include <glib.h> + +#include <string.h> + +const struct tag_table ape_tags[] = { + { "album artist", TAG_ALBUM_ARTIST }, + { "year", TAG_DATE }, + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; + +static enum tag_type +tag_ape_name_parse(const char *name) +{ + enum tag_type type = tag_table_lookup_i(ape_tags, name); + if (type == TAG_NUM_OF_ITEM_TYPES) + type = tag_name_parse_i(name); + + return type; +} + +/** + * @return true if the item was recognized + */ +static bool +tag_ape_import_item(unsigned long flags, + const char *key, const char *value, size_t value_length, + const struct tag_handler *handler, void *handler_ctx) +{ + /* we only care about utf-8 text tags */ + if ((flags & (0x3 << 1)) != 0) + return false; + + tag_handler_invoke_pair(handler, handler_ctx, key, value); + + enum tag_type type = tag_ape_name_parse(key); + if (type == TAG_NUM_OF_ITEM_TYPES) + return false; + + bool recognized = false; + const char *end = value + value_length; + while (true) { + /* multiple values are separated by null bytes */ + const char *n = (const char *)memchr(value, 0, end - value); + if (n != nullptr) { + if (n > value) { + tag_handler_invoke_tag(handler, handler_ctx, + type, value); + recognized = true; + } + + value = n + 1; + } else { + char *p = g_strndup(value, end - value); + tag_handler_invoke_tag(handler, handler_ctx, + type, p); + g_free(p); + recognized = true; + break; + } + } + + return recognized; +} + +bool +tag_ape_scan2(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + bool recognized = false; + + auto callback = [handler, handler_ctx, &recognized] + (unsigned long flags, const char *key, + const char *value, + size_t value_length) { + recognized |= tag_ape_import_item(flags, key, value, + value_length, + handler, handler_ctx); + return true; + }; + + return tag_ape_scan(path_fs, callback) && recognized; +} diff --git a/src/tag/ApeTag.hxx b/src/tag/ApeTag.hxx new file mode 100644 index 000000000..1a7143314 --- /dev/null +++ b/src/tag/ApeTag.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_TAG_HXX +#define MPD_APE_TAG_HXX + +#include "TagTable.hxx" + +struct tag_handler; + +extern const struct tag_table ape_tags[]; + +/** + * Scan the APE tags of a file. + * + * @param path_fs the path of the file in filesystem encoding + */ +bool +tag_ape_scan2(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/tag/Riff.cxx b/src/tag/Riff.cxx new file mode 100644 index 000000000..d756ebc30 --- /dev/null +++ b/src/tag/Riff.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Riff.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> + +static constexpr Domain riff_domain("riff"); + +struct riff_header { + char id[4]; + uint32_t size; + char format[4]; +}; + +struct riff_chunk_header { + char id[4]; + uint32_t size; +}; + +size_t +riff_seek_id3(FILE *file) +{ + /* determine the file size */ + + struct stat st; + if (fstat(fileno(file), &st) < 0) { + LogErrno(riff_domain, "Failed to stat file descriptor"); + return 0; + } + + /* seek to the beginning and read the RIFF header */ + + if (fseek(file, 0, SEEK_SET) != 0) { + LogErrno(riff_domain, "Failed to seek"); + return 0; + } + + riff_header header; + size_t size = fread(&header, sizeof(header), 1, file); + if (size != 1 || + memcmp(header.id, "RIFF", 4) != 0 || + GUINT32_FROM_LE(header.size) > (uint32_t)st.st_size) + /* not a RIFF file */ + return 0; + + while (true) { + /* read the chunk header */ + + riff_chunk_header chunk; + size = fread(&chunk, sizeof(chunk), 1, file); + if (size != 1) + return 0; + + size = GUINT32_FROM_LE(chunk.size); + if (size > G_MAXINT32) + /* too dangerous, bail out: possible integer + underflow when casting to off_t */ + return 0; + + if (size % 2 != 0) + /* pad byte */ + ++size; + + if (memcmp(chunk.id, "id3 ", 4) == 0) + /* found it! */ + return size; + + if (fseek(file, size, SEEK_CUR) != 0) + return 0; + } +} diff --git a/src/tag/Riff.hxx b/src/tag/Riff.hxx new file mode 100644 index 000000000..fbbdfaaf6 --- /dev/null +++ b/src/tag/Riff.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 parser for the RIFF file format (e.g. WAV). + */ + +#ifndef MPD_RIFF_HXX +#define MPD_RIFF_HXX + +#include <stddef.h> +#include <stdio.h> + +/** + * Seeks the RIFF file to the ID3 chunk. + * + * @return the size of the ID3 chunk on success, or 0 if this is not a + * RIFF file or no ID3 chunk was found + */ +size_t +riff_seek_id3(FILE *file); + +#endif diff --git a/src/tag/Tag.cxx b/src/tag/Tag.cxx new file mode 100644 index 000000000..8e6766f80 --- /dev/null +++ b/src/tag/Tag.cxx @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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.hxx" +#include "TagPool.hxx" +#include "TagString.hxx" +#include "TagSettings.h" + +#include <glib.h> +#include <assert.h> +#include <string.h> + +enum tag_type +tag_name_parse(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != nullptr); + + if (strcmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +enum tag_type +tag_name_parse_i(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != nullptr); + + if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +static size_t +items_size(const Tag &tag) +{ + return tag.num_items * sizeof(TagItem *); +} + +void +Tag::Clear() +{ + time = -1; + has_playlist = false; + + tag_pool_lock.lock(); + for (unsigned i = 0; i < num_items; ++i) + tag_pool_put_item(items[i]); + tag_pool_lock.unlock(); + + g_free(items); + items = nullptr; + num_items = 0; +} + +Tag::~Tag() +{ + tag_pool_lock.lock(); + for (int i = num_items; --i >= 0; ) + tag_pool_put_item(items[i]); + tag_pool_lock.unlock(); + + g_free(items); +} + +Tag::Tag(const Tag &other) + :time(other.time), has_playlist(other.has_playlist), + items(nullptr), + num_items(other.num_items) +{ + if (num_items > 0) { + items = (TagItem **)g_malloc(items_size(other)); + + tag_pool_lock.lock(); + for (unsigned i = 0; i < num_items; i++) + items[i] = tag_pool_dup_item(other.items[i]); + tag_pool_lock.unlock(); + } +} + +Tag * +Tag::Merge(const Tag &base, const Tag &add) +{ + unsigned n; + + /* allocate new tag object */ + + Tag *ret = new Tag(); + ret->time = add.time > 0 ? add.time : base.time; + ret->num_items = base.num_items + add.num_items; + ret->items = ret->num_items > 0 + ? (TagItem **)g_malloc(items_size(*ret)) + : nullptr; + + tag_pool_lock.lock(); + + /* copy all items from "add" */ + + for (unsigned i = 0; i < add.num_items; ++i) + ret->items[i] = tag_pool_dup_item(add.items[i]); + + n = add.num_items; + + /* copy additional items from "base" */ + + for (unsigned i = 0; i < base.num_items; ++i) + if (!add.HasType(base.items[i]->type)) + ret->items[n++] = tag_pool_dup_item(base.items[i]); + + tag_pool_lock.unlock(); + + assert(n <= ret->num_items); + + if (n < ret->num_items) { + /* some tags were not copied - shrink ret->items */ + assert(n > 0); + + ret->num_items = n; + ret->items = (TagItem **) + g_realloc(ret->items, items_size(*ret)); + } + + return ret; +} + +Tag * +Tag::MergeReplace(Tag *base, Tag *add) +{ + if (add == nullptr) + return base; + + if (base == nullptr) + return add; + + Tag *tag = Merge(*base, *add); + delete base; + delete add; + + return tag; +} + +const char * +Tag::GetValue(tag_type type) const +{ + assert(type < TAG_NUM_OF_ITEM_TYPES); + + for (unsigned i = 0; i < num_items; i++) + if (items[i]->type == type) + return items[i]->value; + + return nullptr; +} + +bool +Tag::HasType(tag_type type) const +{ + return GetValue(type) != nullptr; +} + +void +Tag::AddItemInternal(tag_type type, const char *value, size_t len) +{ + unsigned int i = num_items; + + char *p = FixTagString(value, len); + if (p != nullptr) { + value = p; + len = strlen(value); + } + + num_items++; + + items = (TagItem **)g_realloc(items, items_size(*this)); + + tag_pool_lock.lock(); + items[i] = tag_pool_get_item(type, value, len); + tag_pool_lock.unlock(); + + g_free(p); +} + +void +Tag::AddItem(tag_type type, const char *value, size_t len) +{ + if (ignore_tag_items[type]) + return; + + if (value == nullptr || len == 0) + return; + + AddItemInternal(type, value, len); +} + +void +Tag::AddItem(tag_type type, const char *value) +{ + AddItem(type, value, strlen(value)); +} diff --git a/src/tag/Tag.hxx b/src/tag/Tag.hxx new file mode 100644 index 000000000..395780cde --- /dev/null +++ b/src/tag/Tag.hxx @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_TAG_HXX + +#include "TagType.h" +#include "TagItem.hxx" +#include "gcc.h" + +#include <algorithm> + +#include <stddef.h> + +/** + * The meta information about a song file. It is a MPD specific + * subset of tags (e.g. from ID3, vorbis comments, ...). + */ +struct Tag { + /** + * The duration of the song (in seconds). A value of zero + * means that the length is unknown. If the duration is + * really between zero and one second, you should round up to + * 1. + */ + int time; + + /** + * Does this file have an embedded playlist (e.g. embedded CUE + * sheet)? + */ + bool has_playlist; + + /** an array of tag items */ + TagItem **items; + + /** the total number of tag items in the #items array */ + unsigned num_items; + + /** + * Create an empty tag. + */ + Tag():time(-1), has_playlist(false), + items(nullptr), num_items(0) {} + + Tag(const Tag &other); + + Tag(Tag &&other) + :time(other.time), has_playlist(other.has_playlist), + items(other.items), num_items(other.num_items) { + other.items = nullptr; + other.num_items = 0; + } + + /** + * Free the tag object and all its items. + */ + ~Tag(); + + Tag &operator=(const Tag &other) = delete; + + Tag &operator=(Tag &&other) { + time = other.time; + has_playlist = other.has_playlist; + std::swap(items, other.items); + std::swap(num_items, other.num_items); + return *this; + } + + /** + * Returns true if the tag contains no items. This ignores the "time" + * attribute. + */ + bool IsEmpty() const { + return num_items == 0; + } + + /** + * Returns true if the tag contains any information. + */ + bool IsDefined() const { + return !IsEmpty() || time >= 0; + } + + /** + * Clear everything, as if this was a new Tag object. + */ + void Clear(); + + /** + * Appends a new tag item. + * + * @param type the type of the new tag item + * @param value the value of the tag item (not null-terminated) + * @param len the length of #value + */ + void AddItem(tag_type type, const char *value, size_t len); + + /** + * Appends a new tag item. + * + * @param tag the #tag object + * @param type the type of the new tag item + * @param value the value of the tag item (null-terminated) + */ + void AddItem(tag_type type, const char *value); + + /** + * Merges the data from two tags. If both tags share data for the + * same tag_type, only data from "add" is used. + * + * @return a newly allocated tag + */ + gcc_malloc + static Tag *Merge(const Tag &base, const Tag &add); + + /** + * Merges the data from two tags. Any of the two may be NULL. Both + * are freed by this function. + * + * @return a newly allocated tag + */ + gcc_malloc + static Tag *MergeReplace(Tag *base, Tag *add); + + /** + * Returns the first value of the specified tag type, or NULL if none + * is present in this tag object. + */ + gcc_pure + const char *GetValue(tag_type type) const; + + /** + * Checks whether the tag contains one or more items with + * the specified type. + */ + gcc_pure + bool HasType(tag_type type) const; + +private: + void AddItemInternal(tag_type type, const char *value, size_t len); +}; + +/** + * Parse the string, and convert it into a #tag_type. Returns + * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. + */ +gcc_pure +enum tag_type +tag_name_parse(const char *name); + +/** + * Parse the string, and convert it into a #tag_type. Returns + * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. + * + * Case does not matter. + */ +gcc_pure +enum tag_type +tag_name_parse_i(const char *name); + +#endif diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx new file mode 100644 index 000000000..23409fe33 --- /dev/null +++ b/src/tag/TagBuilder.cxx @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagBuilder.hxx" +#include "TagSettings.h" +#include "TagPool.hxx" +#include "TagString.hxx" +#include "Tag.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +void +TagBuilder::Clear() +{ + time = -1; + has_playlist = false; + + tag_pool_lock.lock(); + for (auto i : items) + tag_pool_put_item(i); + tag_pool_lock.unlock(); + + items.clear(); +} + +void +TagBuilder::Commit(Tag &tag) +{ + tag.Clear(); + + tag.time = time; + tag.has_playlist = has_playlist; + + /* move all TagItem pointers to the new Tag object without + touching the TagPool reference counters; the + vector::clear() call is important to detach them from this + object */ + const unsigned n_items = items.size(); + tag.num_items = n_items; + tag.items = g_new(TagItem *, n_items); + std::copy_n(items.begin(), n_items, tag.items); + items.clear(); + + /* now ensure that this object is fresh (will not delete any + items because we've already moved them out) */ + Clear(); +} + +Tag * +TagBuilder::Commit() +{ + Tag *tag = new Tag(); + Commit(*tag); + return tag; +} + +inline void +TagBuilder::AddItemInternal(tag_type type, const char *value, size_t length) +{ + assert(value != nullptr); + assert(length > 0); + + char *p = FixTagString(value, length); + if (p != nullptr) { + value = p; + length = strlen(value); + } + + tag_pool_lock.lock(); + auto i = tag_pool_get_item(type, value, length); + tag_pool_lock.unlock(); + + g_free(p); + + items.push_back(i); +} + +void +TagBuilder::AddItem(tag_type type, const char *value, size_t length) +{ + assert(value != nullptr); + + if (length == 0 || ignore_tag_items[type]) + return; + + AddItemInternal(type, value, length); +} + +void +TagBuilder::AddItem(tag_type type, const char *value) +{ + assert(value != nullptr); + + AddItem(type, value, strlen(value)); +} diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx new file mode 100644 index 000000000..63a14a946 --- /dev/null +++ b/src/tag/TagBuilder.hxx @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_BUILDER_HXX +#define MPD_TAG_BUILDER_HXX + +#include "TagType.h" +#include "gcc.h" + +#include <vector> + +#include <stddef.h> + +struct TagItem; +struct Tag; + +/** + * A class that constructs #Tag objects. + */ +class TagBuilder { + /** + * The duration of the song (in seconds). A value of zero + * means that the length is unknown. If the duration is + * really between zero and one second, you should round up to + * 1. + */ + int time; + + /** + * Does this file have an embedded playlist (e.g. embedded CUE + * sheet)? + */ + bool has_playlist; + + /** an array of tag items */ + std::vector<TagItem *> items; + +public: + /** + * Create an empty tag. + */ + TagBuilder() + :time(-1), has_playlist(false) {} + + ~TagBuilder() { + Clear(); + } + + TagBuilder(const TagBuilder &other) = delete; + TagBuilder &operator=(const TagBuilder &other) = delete; + + /** + * Returns true if the tag contains no items. This ignores the "time" + * attribute. + */ + bool IsEmpty() const { + return items.empty(); + } + + /** + * Returns true if the object contains any information. + */ + gcc_pure + bool IsDefined() const { + return time >= 0 || has_playlist || !IsEmpty(); + } + + void Clear(); + + /** + * Move this object to the given #Tag instance. This object + * is empty afterwards. + */ + void Commit(Tag &tag); + + /** + * Create a new #Tag instance from data in this object. The + * returned object is owned by the caller. This object is + * empty afterwards. + */ + Tag *Commit(); + + void SetTime(int _time) { + time = _time; + } + + void SetHasPlaylist(bool _has_playlist) { + has_playlist = _has_playlist; + } + + void Reserve(unsigned n) { + items.reserve(n); + } + + /** + * Appends a new tag item. + * + * @param type the type of the new tag item + * @param value the value of the tag item (not null-terminated) + * @param len the length of #value + */ + gcc_nonnull_all + void AddItem(tag_type type, const char *value, size_t length); + + /** + * Appends a new tag item. + * + * @param type the type of the new tag item + * @param value the value of the tag item (null-terminated) + */ + gcc_nonnull_all + void AddItem(tag_type type, const char *value); + +private: + gcc_nonnull_all + void AddItemInternal(tag_type type, const char *value, size_t length); +}; + +#endif diff --git a/src/tag/TagConfig.cxx b/src/tag/TagConfig.cxx new file mode 100644 index 000000000..9e47cd05b --- /dev/null +++ b/src/tag/TagConfig.cxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagConfig.hxx" +#include "TagSettings.h" +#include "Tag.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <algorithm> + +#include <string.h> + +void +TagLoadConfig() +{ + const char *value = config_get_string(CONF_METADATA_TO_USE, nullptr); + if (value == nullptr) + return; + + std::fill_n(ignore_tag_items, TAG_NUM_OF_ITEM_TYPES, true); + + if (0 == g_ascii_strcasecmp(value, "none")) + return; + + bool quit = false; + char *temp, *c, *s; + temp = c = s = g_strdup(value); + do { + if (*s == ',' || *s == '\0') { + if (*s == '\0') + quit = true; + *s = '\0'; + + c = g_strstrip(c); + if (*c == 0) + continue; + + const auto type = tag_name_parse_i(c); + if (type == TAG_NUM_OF_ITEM_TYPES) + FormatFatalError("error parsing metadata item \"%s\"", + c); + + ignore_tag_items[type] = false; + + s++; + c = s; + } + s++; + } while (!quit); + + g_free(temp); +} diff --git a/src/tag/TagConfig.hxx b/src/tag/TagConfig.hxx new file mode 100644 index 000000000..5ec6766d4 --- /dev/null +++ b/src/tag/TagConfig.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CONFIG_HXX +#define MPD_TAG_CONFIG_HXX + +#include "TagType.h" + +void +TagLoadConfig(); + +#endif diff --git a/src/tag/TagHandler.cxx b/src/tag/TagHandler.cxx new file mode 100644 index 000000000..859f92b20 --- /dev/null +++ b/src/tag/TagHandler.cxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagHandler.hxx" +#include "TagBuilder.hxx" + +#include <glib.h> + +static void +add_tag_duration(unsigned seconds, void *ctx) +{ + TagBuilder &tag = *(TagBuilder *)ctx; + + tag.SetTime(seconds); +} + +static void +add_tag_tag(enum tag_type type, const char *value, void *ctx) +{ + TagBuilder &tag = *(TagBuilder *)ctx; + + tag.AddItem(type, value); +} + +const struct tag_handler add_tag_handler = { + add_tag_duration, + add_tag_tag, + nullptr, +}; + +static void +full_tag_pair(const char *name, gcc_unused const char *value, void *ctx) +{ + TagBuilder &tag = *(TagBuilder *)ctx; + + if (g_ascii_strcasecmp(name, "cuesheet") == 0) + tag.SetHasPlaylist(true); +} + +const struct tag_handler full_tag_handler = { + add_tag_duration, + add_tag_tag, + full_tag_pair, +}; + diff --git a/src/tag/TagHandler.hxx b/src/tag/TagHandler.hxx new file mode 100644 index 000000000..36871c962 --- /dev/null +++ b/src/tag/TagHandler.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_HANDLER_HXX +#define MPD_TAG_HANDLER_HXX + +#include "check.h" +#include "TagType.h" + +#include <assert.h> + +/** + * A callback table for receiving metadata of a song. + */ +struct tag_handler { + /** + * Declare the duration of a song, in seconds. Do not call + * this when the duration could not be determined, because + * there is no magic value for "unknown duration". + */ + void (*duration)(unsigned seconds, void *ctx); + + /** + * A tag has been read. + * + * @param the value of the tag; the pointer will become + * invalid after returning + */ + void (*tag)(enum tag_type type, const char *value, void *ctx); + + /** + * A name-value pair has been read. It is the codec specific + * representation of tags. + */ + void (*pair)(const char *key, const char *value, void *ctx); +}; + +static inline void +tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx, + unsigned seconds) +{ + assert(handler != nullptr); + + if (handler->duration != nullptr) + handler->duration(seconds, ctx); +} + +static inline void +tag_handler_invoke_tag(const struct tag_handler *handler, void *ctx, + enum tag_type type, const char *value) +{ + assert(handler != nullptr); + assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES); + assert(value != nullptr); + + if (handler->tag != nullptr) + handler->tag(type, value, ctx); +} + +static inline void +tag_handler_invoke_pair(const struct tag_handler *handler, void *ctx, + const char *name, const char *value) +{ + assert(handler != nullptr); + assert(name != nullptr); + assert(value != nullptr); + + if (handler->pair != nullptr) + handler->pair(name, value, ctx); +} + +/** + * This #tag_handler implementation adds tag values to a #TagBuilder object + * (casted from the context pointer). + */ +extern const struct tag_handler add_tag_handler; + +/** + * This #tag_handler implementation adds tag values to a #TagBuilder object + * (casted from the context pointer), and supports the has_playlist + * attribute. + */ +extern const struct tag_handler full_tag_handler; + +#endif diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx new file mode 100644 index 000000000..2f32ef6e5 --- /dev/null +++ b/src/tag/TagId3.cxx @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagId3.hxx" +#include "TagHandler.hxx" +#include "TagTable.hxx" +#include "Tag.hxx" +#include "TagBuilder.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "ConfigGlobal.hxx" +#include "Riff.hxx" +#include "Aiff.hxx" + +#include <glib.h> +#include <id3tag.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +# ifndef ID3_FRAME_COMPOSER +# define ID3_FRAME_COMPOSER "TCOM" +# 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" /* this one is unofficial, introduced by Itunes */ +#endif + +#ifndef ID3_FRAME_ALBUM_ARTIST +#define ID3_FRAME_ALBUM_ARTIST "TPE2" +#endif + +static constexpr Domain id3_domain("id3"); + +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) +{ + union id3_field *field; + const id3_ucs4_t *ucs4; + + field = id3_frame_field(frame, i); + if (field == nullptr) + return nullptr; + + ucs4 = id3_field_getstring(field); + if (ucs4 == nullptr) + return nullptr; + + return id3_ucs4_utf8duplicate(ucs4); +} + +/* This will try to convert a string to utf-8, + */ +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; + + /* use encoding field here? */ + if (is_id3v1 && + (encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) { + isostr = id3_ucs4_latin1duplicate(ucs4); + if (G_UNLIKELY(!isostr)) { + return nullptr; + } + + utf8 = (id3_utf8_t *) + g_convert_with_fallback((const char*)isostr, -1, + "utf-8", encoding, + nullptr, nullptr, + nullptr, nullptr); + if (utf8 == nullptr) { + FormatWarning(id3_domain, + "Unable to convert %s string to UTF-8: '%s'", + encoding, isostr); + g_free(isostr); + return nullptr; + } + g_free(isostr); + } else { + utf8 = id3_ucs4_utf8duplicate(ucs4); + if (G_UNLIKELY(!utf8)) { + return nullptr; + } + } + + utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8)); + g_free(utf8); + + return utf8_stripped; +} + +/** + * Import a "Text information frame" (ID3v2.4.0 section 4.2). It + * contains 2 fields: + * + * - encoding + * - string list + */ +static void +tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, + enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) +{ + id3_ucs4_t const *ucs4; + id3_utf8_t *utf8; + union id3_field const *field; + unsigned int nstrings, i; + + if (frame->nfields != 2) + return; + + /* check the encoding field */ + + field = id3_frame_field(frame, 0); + if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING) + return; + + /* process the value(s) */ + + field = id3_frame_field(frame, 1); + if (field == nullptr || 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 == nullptr) + continue; + + if (type == TAG_GENRE) + ucs4 = id3_genre_name(ucs4); + + utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + if (utf8 == nullptr) + continue; + + tag_handler_invoke_tag(handler, handler_ctx, + type, (const char *)utf8); + g_free(utf8); + } +} + +/** + * 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 id3_tag *tag, const char *id, enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) +{ + const struct id3_frame *frame; + for (unsigned i = 0; + (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i) + tag_id3_import_text_frame(tag, frame, type, + handler, handler_ctx); +} + +/** + * 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 id3_tag *tag, + const struct id3_frame *frame, enum tag_type type, + const struct tag_handler *handler, + void *handler_ctx) +{ + id3_ucs4_t const *ucs4; + id3_utf8_t *utf8; + 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 == nullptr) + return; + + ucs4 = id3_field_getfullstring(field); + if (ucs4 == nullptr) + return; + + utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + if (utf8 == nullptr) + return; + + tag_handler_invoke_tag(handler, handler_ctx, type, (const 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 id3_tag *tag, const char *id, enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) +{ + const struct id3_frame *frame; + for (unsigned i = 0; + (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i) + tag_id3_import_comment_frame(tag, frame, type, + handler, handler_ctx); +} + +/** + * Parse a TXXX name, and convert it to a tag_type enum value. + * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood. + */ +static enum tag_type +tag_id3_parse_txxx_name(const char *name) +{ + static const struct tag_table txxx_tags[] = { + { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT }, + { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID }, + { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID }, + { "MusicBrainz Album Artist Id", + TAG_MUSICBRAINZ_ALBUMARTISTID }, + { "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID }, + { nullptr, TAG_NUM_OF_ITEM_TYPES } + }; + + return tag_table_lookup(txxx_tags, name); +} + +/** + * Import all known MusicBrainz tags from TXXX frames. + */ +static void +tag_id3_import_musicbrainz(struct id3_tag *id3_tag, + const struct tag_handler *handler, + void *handler_ctx) +{ + for (unsigned i = 0;; ++i) { + const struct id3_frame *frame; + id3_utf8_t *name, *value; + enum tag_type type; + + frame = id3_tag_findframe(id3_tag, "TXXX", i); + if (frame == nullptr) + break; + + name = tag_id3_getstring(frame, 1); + if (name == nullptr) + continue; + + value = tag_id3_getstring(frame, 2); + if (value == nullptr) + continue; + + tag_handler_invoke_pair(handler, handler_ctx, + (const char *)name, + (const char *)value); + + type = tag_id3_parse_txxx_name((const char*)name); + free(name); + + if (type != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, handler_ctx, + type, (const char*)value); + + free(value); + } +} + +/** + * Imports the MusicBrainz TrackId from the UFID tag. + */ +static void +tag_id3_import_ufid(struct id3_tag *id3_tag, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0;; ++i) { + const struct id3_frame *frame; + union id3_field *field; + const id3_latin1_t *name; + const id3_byte_t *value; + id3_length_t length; + + frame = id3_tag_findframe(id3_tag, "UFID", i); + if (frame == nullptr) + break; + + field = id3_frame_field(frame, 0); + if (field == nullptr) + continue; + + name = id3_field_getlatin1(field); + if (name == nullptr || + strcmp((const char *)name, "http://musicbrainz.org") != 0) + continue; + + field = id3_frame_field(frame, 1); + if (field == nullptr) + continue; + + value = id3_field_getbinarydata(field, &length); + if (value == nullptr || length == 0) + continue; + + char *p = g_strndup((const char *)value, length); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_MUSICBRAINZ_TRACKID, p); + g_free(p); + } +} + +void +scan_id3_tag(struct id3_tag *tag, + const struct tag_handler *handler, void *handler_ctx) +{ + tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST, + TAG_ALBUM_ARTIST, handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT, + TAG_ARTIST_SORT, handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT, + TAG_ALBUM_ARTIST_SORT, handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER, + handler, handler_ctx); + tag_id3_import_text(tag, "TPE3", TAG_PERFORMER, + handler, handler_ctx); + tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx); + tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC, + handler, handler_ctx); + + tag_id3_import_musicbrainz(tag, handler, handler_ctx); + tag_id3_import_ufid(tag, handler, handler_ctx); +} + +Tag * +tag_id3_import(struct id3_tag *tag) +{ + TagBuilder tag_builder; + scan_id3_tag(tag, &add_tag_handler, &tag_builder); + return tag_builder.IsEmpty() + ? nullptr + : tag_builder.Commit(); +} + +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 +get_id3v2_footer_size(FILE *stream, long offset, int whence) +{ + id3_byte_t buf[ID3_TAG_QUERYSIZE]; + int bufsize; + + 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 * +tag_id3_read(FILE *stream, long offset, int whence) +{ + struct id3_tag *tag; + id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; + int tag_size; + int query_buffer_size; + + /* It's ok if we get less than we asked for */ + query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, + stream, offset, whence); + if (query_buffer_size <= 0) + return nullptr; + + /* Look for a tag header */ + tag_size = id3_tag_query(query_buffer, query_buffer_size); + if (tag_size <= 0) return nullptr; + + /* Found a tag. Allocate a buffer and read it in. */ + id3_byte_t *tag_buffer = (id3_byte_t *)g_malloc(tag_size); + if (!tag_buffer) + return nullptr; + + int tag_buffer_size = fill_buffer(tag_buffer, tag_size, + stream, offset, whence); + if (tag_buffer_size < tag_size) { + g_free(tag_buffer); + return nullptr; + } + + tag = id3_tag_parse(tag_buffer, tag_buffer_size); + + g_free(tag_buffer); + + return tag; +} + +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 = tag_id3_read(stream, 0, SEEK_SET); + if (!tag) { + return nullptr; + } else if (tag_is_id3v1(tag)) { + /* id3v1 tags don't belong here */ + id3_tag_delete(tag); + return nullptr; + } + + /* We have an id3v2 tag, so let's look for SEEK frames */ + while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { + /* Found a SEEK frame, get it's value */ + seek = id3_field_getint(id3_frame_field(frame, 0)); + if (seek < 0) + break; + + /* Get the tag specified by the SEEK frame */ + seektag = tag_id3_read(stream, seek, SEEK_CUR); + if (!seektag || tag_is_id3v1(seektag)) + break; + + /* Replace the old tag with the new one */ + id3_tag_delete(tag); + tag = seektag; + } + + return tag; +} + +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 = tag_id3_read(stream, -128, SEEK_END); + + /* Get the id3v2 tag size from the footer (located before v1tag) */ + 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 = tag_id3_read(stream, tagsize, SEEK_CUR); + if (!tag) + return v1tag; + + /* We have an id3v2 tag, so ditch v1tag */ + id3_tag_delete(v1tag); + + return tag; +} + +static struct id3_tag * +tag_id3_riff_aiff_load(FILE *file) +{ + size_t size = riff_seek_id3(file); + if (size == 0) + size = aiff_seek_id3(file); + if (size == 0) + return nullptr; + + if (size > 4 * 1024 * 1024) + /* too large, don't allocate so much memory */ + return nullptr; + + id3_byte_t *buffer = (id3_byte_t *)g_malloc(size); + size_t ret = fread(buffer, size, 1, file); + if (ret != 1) { + LogWarning(id3_domain, "Failed to read RIFF chunk"); + g_free(buffer); + return nullptr; + } + + struct id3_tag *tag = id3_tag_parse(buffer, size); + g_free(buffer); + return tag; +} + +struct id3_tag * +tag_id3_load(const char *path_fs, Error &error) +{ + FILE *file = fopen(path_fs, "rb"); + if (file == nullptr) { + error.FormatErrno("Failed to open file %s", path_fs); + return nullptr; + } + + struct id3_tag *tag = tag_id3_find_from_beginning(file); + if (tag == nullptr) { + tag = tag_id3_riff_aiff_load(file); + if (tag == nullptr) + tag = tag_id3_find_from_end(file); + } + + fclose(file); + return tag; +} + +bool +tag_id3_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + Error error; + struct id3_tag *tag = tag_id3_load(path_fs, error); + if (tag == nullptr) { + if (error.IsDefined()) + LogError(error); + + return false; + } + + scan_id3_tag(tag, handler, handler_ctx); + id3_tag_delete(tag); + return true; +} diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx new file mode 100644 index 000000000..ca288754b --- /dev/null +++ b/src/tag/TagId3.hxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ID3_HXX +#define MPD_TAG_ID3_HXX + +#include "check.h" +#include "gcc.h" + +struct tag_handler; +struct Tag; +struct id3_tag; +class Error; + +#ifdef HAVE_ID3TAG + +bool +tag_id3_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); + +Tag * +tag_id3_import(struct id3_tag *); + +/** + * Loads the ID3 tags from the file into a libid3tag object. The + * return value must be freed with id3_tag_delete(). + * + * @return NULL on error or if no ID3 tag was found in the file (no + * Error will be set) + */ +struct id3_tag * +tag_id3_load(const char *path_fs, Error &error); + +/** + * Import all tags from the provided id3_tag *tag + * + */ +void +scan_id3_tag(struct id3_tag *tag, + const struct tag_handler *handler, void *handler_ctx); + +#else + +static inline bool +tag_id3_scan(gcc_unused const char *path_fs, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + return false; +} + +#endif + +#endif diff --git a/src/tag/TagItem.hxx b/src/tag/TagItem.hxx new file mode 100644 index 000000000..a2924f2af --- /dev/null +++ b/src/tag/TagItem.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ITEM_HXX +#define MPD_TAG_ITEM_HXX + +#include "TagType.h" +#include "gcc.h" + +/** + * One tag value. It is a mapping of #tag_type to am arbitrary string + * value. Each tag can have multiple items of one tag type (although + * few clients support that). + */ +struct TagItem { + /** the type of this item */ + enum tag_type type; + + /** + * the value of this tag; this is a variable length string + */ + char value[sizeof(long) - sizeof(type)]; + + TagItem() = default; + TagItem(const TagItem &other) = delete; + TagItem &operator=(const TagItem &other) = delete; +} gcc_packed; + +#endif diff --git a/src/tag/TagNames.c b/src/tag/TagNames.c new file mode 100644 index 000000000..eac6fc59a --- /dev/null +++ b/src/tag/TagNames.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagType.h" + +const char *const tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { + [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", + [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID", + [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID", + [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID", +}; diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx new file mode 100644 index 000000000..57a9137a7 --- /dev/null +++ b/src/tag/TagPool.cxx @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagPool.hxx" +#include "TagItem.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +Mutex tag_pool_lock; + +#define NUM_SLOTS 4096 + +struct slot { + struct slot *next; + unsigned char ref; + TagItem item; +} mpd_packed; + +static struct slot *slots[NUM_SLOTS]; + +static inline unsigned +calc_hash_n(enum tag_type type, const char *p, size_t length) +{ + unsigned hash = 5381; + + assert(p != nullptr); + + while (length-- > 0) + hash = (hash << 5) + hash + *p++; + + return hash ^ type; +} + +static inline unsigned +calc_hash(enum tag_type type, const char *p) +{ + unsigned hash = 5381; + + assert(p != nullptr); + + while (*p != 0) + hash = (hash << 5) + hash + *p++; + + return hash ^ type; +} + +static inline struct slot * +tag_item_to_slot(TagItem *item) +{ + return (struct slot*)(((char*)item) - offsetof(struct slot, item)); +} + +static struct slot *slot_alloc(struct slot *next, + enum tag_type type, + const char *value, int length) +{ + struct slot *slot; + + slot = (struct slot *) + g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); + slot->next = next; + slot->ref = 1; + slot->item.type = type; + memcpy(slot->item.value, value, length); + slot->item.value[length] = 0; + return slot; +} + +TagItem * +tag_pool_get_item(enum tag_type type, const char *value, size_t length) +{ + struct slot **slot_p, *slot; + + slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; + for (slot = *slot_p; slot != nullptr; slot = slot->next) { + if (slot->item.type == type && + length == strlen(slot->item.value) && + memcmp(value, slot->item.value, length) == 0 && + slot->ref < 0xff) { + assert(slot->ref > 0); + ++slot->ref; + return &slot->item; + } + } + + slot = slot_alloc(*slot_p, type, value, length); + *slot_p = slot; + return &slot->item; +} + +TagItem * +tag_pool_dup_item(TagItem *item) +{ + struct slot *slot = tag_item_to_slot(item); + + assert(slot->ref > 0); + + if (slot->ref < 0xff) { + ++slot->ref; + return item; + } else { + /* the reference counter overflows above 0xff; + duplicate the item, and start with 1 */ + size_t length = strlen(item->value); + struct slot **slot_p = + &slots[calc_hash_n(item->type, item->value, + length) % NUM_SLOTS]; + slot = slot_alloc(*slot_p, item->type, + item->value, strlen(item->value)); + *slot_p = slot; + return &slot->item; + } +} + +void +tag_pool_put_item(TagItem *item) +{ + struct slot **slot_p, *slot; + + slot = tag_item_to_slot(item); + assert(slot->ref > 0); + --slot->ref; + + if (slot->ref > 0) + return; + + for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; + *slot_p != slot; + slot_p = &(*slot_p)->next) { + assert(*slot_p != nullptr); + } + + *slot_p = slot->next; + g_free(slot); +} diff --git a/src/tag/TagPool.hxx b/src/tag/TagPool.hxx new file mode 100644 index 000000000..a6b28b355 --- /dev/null +++ b/src/tag/TagPool.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_POOL_HXX +#define MPD_TAG_POOL_HXX + +#include "TagType.h" +#include "thread/Mutex.hxx" + +extern Mutex tag_pool_lock; + +struct TagItem; + +TagItem * +tag_pool_get_item(enum tag_type type, const char *value, size_t length); + +TagItem * +tag_pool_dup_item(TagItem *item); + +void +tag_pool_put_item(TagItem *item); + +#endif diff --git a/src/tag/TagRva2.cxx b/src/tag/TagRva2.cxx new file mode 100644 index 000000000..071e3a443 --- /dev/null +++ b/src/tag/TagRva2.cxx @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagRva2.hxx" +#include "ReplayGainInfo.hxx" + +#include <stdint.h> +#include <string.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, const id3_latin1_t *id) +{ + if (data->type != CHANNEL_MASTER_VOLUME) + return false; + + float volume_adjustment = rva2_float_volume_adjustment(data); + + if (strcmp((const char *)id, "album") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment; + } else if (strcmp((const char *)id, "track") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment; + } else { + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment; + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment; + } + + return true; +} + +static bool +rva2_apply_frame(struct replay_gain_info *replay_gain_info, + const struct id3_frame *frame) +{ + const id3_latin1_t *id = id3_field_getlatin1(id3_frame_field(frame, 0)); + id3_length_t length; + const id3_byte_t *data = + id3_field_getbinarydata(id3_frame_field(frame, 1), &length); + + if (id == nullptr || data == nullptr) + 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, id)) + return true; + + data += 4 + peak_bytes; + length -= 4 + peak_bytes; + } + + return false; +} + +bool +tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info) +{ + bool found = false; + + /* Loop through all RVA2 frames as some programs (e.g. mp3gain) store + track and album gain in separate tags */ + const struct id3_frame *frame; + for (unsigned i = 0; + (frame = id3_tag_findframe(tag, "RVA2", i)) != nullptr; + ++i) + if (rva2_apply_frame(replay_gain_info, frame)) + found = true; + + return found; +} diff --git a/src/tag/TagRva2.hxx b/src/tag/TagRva2.hxx new file mode 100644 index 000000000..016a3585d --- /dev/null +++ b/src/tag/TagRva2.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_TAG_RVA2_HXX + +#include "check.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/TagSettings.c b/src/tag/TagSettings.c new file mode 100644 index 000000000..eb2db2ba5 --- /dev/null +++ b/src/tag/TagSettings.c @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagSettings.h" + +bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES] = { + /* ignore comments by default */ + [TAG_COMMENT] = true, +}; diff --git a/src/tag/TagSettings.h b/src/tag/TagSettings.h new file mode 100644 index 000000000..245de2df5 --- /dev/null +++ b/src/tag/TagSettings.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SETTINGS_H +#define MPD_TAG_SETTINGS_H + +#include "TagType.h" + +#include <stdbool.h> + +extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; + +#endif diff --git a/src/tag/TagString.cxx b/src/tag/TagString.cxx new file mode 100644 index 000000000..3e8d8c1b0 --- /dev/null +++ b/src/tag/TagString.cxx @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagString.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +/** + * Replace invalid sequences with the question mark. + */ +static char * +patch_utf8(const char *src, size_t length, const gchar *end) +{ + /* duplicate the string, and replace invalid bytes in that + buffer */ + char *dest = g_strdup(src); + + do { + dest[end - src] = '?'; + } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end)); + + return dest; +} + +static char * +fix_utf8(const char *str, size_t length) +{ + const gchar *end; + char *temp; + gsize written; + + assert(str != nullptr); + + /* check if the string is already valid UTF-8 */ + if (g_utf8_validate(str, length, &end)) + return nullptr; + + /* no, it's not - try to import it from ISO-Latin-1 */ + temp = g_convert(str, length, "utf-8", "iso-8859-1", + nullptr, &written, nullptr); + if (temp != nullptr) + /* success! */ + return temp; + + /* no, still broken - there's no medication, just patch + invalid sequences */ + return patch_utf8(str, length, end); +} + +static bool +char_is_non_printable(unsigned char ch) +{ + return ch < 0x20; +} + +static const char * +find_non_printable(const char *p, size_t length) +{ + for (size_t i = 0; i < length; ++i) + if (char_is_non_printable(p[i])) + return p + i; + + return nullptr; +} + +/** + * Clears all non-printable characters, convert them to space. + * Returns nullptr if nothing needs to be cleared. + */ +static char * +clear_non_printable(const char *p, size_t length) +{ + const char *first = find_non_printable(p, length); + char *dest; + + if (first == nullptr) + return nullptr; + + dest = g_strndup(p, length); + + for (size_t i = first - p; i < length; ++i) + if (char_is_non_printable(dest[i])) + dest[i] = ' '; + + return dest; +} + +char * +FixTagString(const char *p, size_t length) +{ + char *utf8, *cleared; + + utf8 = fix_utf8(p, length); + if (utf8 != nullptr) { + p = utf8; + length = strlen(p); + } + + cleared = clear_non_printable(p, length); + if (cleared == nullptr) + cleared = utf8; + else + g_free(utf8); + + return cleared; +} diff --git a/src/tag/TagString.hxx b/src/tag/TagString.hxx new file mode 100644 index 000000000..21ea139bf --- /dev/null +++ b/src/tag/TagString.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_STRING_HXX +#define MPD_TAG_STRING_HXX + +#include "check.h" +#include "gcc.h" + +#include <stddef.h> + +gcc_malloc gcc_nonnull_all +char * +FixTagString(const char *p, size_t length); + +#endif diff --git a/src/tag/TagTable.cxx b/src/tag/TagTable.cxx new file mode 100644 index 000000000..9c833d5a2 --- /dev/null +++ b/src/tag/TagTable.cxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagTable.hxx" + +#include <glib.h> + +#include <string.h> + +/** + * Looks up a string in a tag translation table (case sensitive). + * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found + * in the table. + */ +tag_type +tag_table_lookup(const struct tag_table *table, const char *name) +{ + for (; table->name != nullptr; ++table) + if (strcmp(name, table->name) == 0) + return table->type; + + return TAG_NUM_OF_ITEM_TYPES; +} + +/** + * Looks up a string in a tag translation table (case insensitive). + * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found + * in the table. + */ +tag_type +tag_table_lookup_i(const struct tag_table *table, const char *name) +{ + for (; table->name != nullptr; ++table) + if (g_ascii_strcasecmp(name, table->name) == 0) + return table->type; + + return TAG_NUM_OF_ITEM_TYPES; +} diff --git a/src/tag/TagTable.hxx b/src/tag/TagTable.hxx new file mode 100644 index 000000000..88bf5b028 --- /dev/null +++ b/src/tag/TagTable.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TAG_TABLE_HXX +#define MPD_TAG_TABLE_HXX + +#include "TagType.h" +#include "gcc.h" + +struct tag_table { + const char *name; + + enum tag_type type; +}; + +/** + * Looks up a string in a tag translation table (case sensitive). + * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found + * in the table. + */ +gcc_pure +tag_type +tag_table_lookup(const tag_table *table, const char *name); + +/** + * Looks up a string in a tag translation table (case insensitive). + * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found + * in the table. + */ +gcc_pure +tag_type +tag_table_lookup_i(const tag_table *table, const char *name); + +#endif diff --git a/src/tag/TagType.h b/src/tag/TagType.h new file mode 100644 index 000000000..118d66a02 --- /dev/null +++ b/src/tag/TagType.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_TYPE_H +#define MPD_TAG_TYPE_H + +#ifdef __cplusplus +#include <stdint.h> +#endif + +/** + * Codes for the type of a tag item. + */ +enum tag_type +#ifdef __cplusplus +/* the size of this enum is 1 byte; this is only relevant for C++ + code; the only C sources including this header don't use instances + of this enum, they only refer to the integer values */ +: uint8_t +#endif + { + 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, + TAG_MUSICBRAINZ_ALBUMARTISTID, + TAG_MUSICBRAINZ_TRACKID, + + TAG_NUM_OF_ITEM_TYPES +}; + +/** + * An array of strings, which map the #tag_type to its machine + * readable name (specific to the MPD protocol). + */ +extern const char *const tag_item_names[]; + +#endif diff --git a/src/tag_ape.c b/src/tag_ape.c deleted file mode 100644 index 0adc43092..000000000 --- a/src/tag_ape.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_ape.h" -#include "tag.h" -#include "tag_table.h" -#include "tag_handler.h" -#include "ape.h" - -const struct tag_table ape_tags[] = { - { "album artist", TAG_ALBUM_ARTIST }, - { "year", TAG_DATE }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - -static enum tag_type -tag_ape_name_parse(const char *name) -{ - enum tag_type type = tag_table_lookup_i(ape_tags, name); - if (type == TAG_NUM_OF_ITEM_TYPES) - type = tag_name_parse_i(name); - - return type; -} - -/** - * @return true if the item was recognized - */ -static bool -tag_ape_import_item(unsigned long flags, - const char *key, const char *value, size_t value_length, - const struct tag_handler *handler, void *handler_ctx) -{ - /* we only care about utf-8 text tags */ - if ((flags & (0x3 << 1)) != 0) - return false; - - tag_handler_invoke_pair(handler, handler_ctx, key, value); - - enum tag_type type = tag_ape_name_parse(key); - if (type == TAG_NUM_OF_ITEM_TYPES) - return false; - - bool recognized = false; - 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_handler_invoke_tag(handler, handler_ctx, - type, value); - recognized = true; - } - - value = n + 1; - } else { - char *p = g_strndup(value, end - value); - tag_handler_invoke_tag(handler, handler_ctx, - type, p); - g_free(p); - recognized = true; - break; - } - } - - return recognized; -} - -struct tag_ape_ctx { - const struct tag_handler *handler; - void *handler_ctx; - - bool recognized; -}; - -static bool -tag_ape_callback(unsigned long flags, const char *key, - const char *value, size_t value_length, void *_ctx) -{ - struct tag_ape_ctx *ctx = _ctx; - - ctx->recognized |= tag_ape_import_item(flags, key, value, value_length, - ctx->handler, ctx->handler_ctx); - return true; -} - -bool -tag_ape_scan2(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - struct tag_ape_ctx ctx = { - .handler = handler, - .handler_ctx = handler_ctx, - .recognized = false, - }; - - return tag_ape_scan(path_fs, tag_ape_callback, &ctx) && - ctx.recognized; -} diff --git a/src/tag_ape.h b/src/tag_ape.h deleted file mode 100644 index e2daf088d..000000000 --- a/src/tag_ape.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_APE_H -#define MPD_TAG_APE_H - -#include "tag_table.h" - -#include <stdbool.h> - -struct tag_handler; - -extern const struct tag_table ape_tags[]; - -/** - * Scan the APE tags of a file. - * - * @param path_fs the path of the file in filesystem encoding - */ -bool -tag_ape_scan2(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/tag_file.c b/src/tag_file.c deleted file mode 100644 index 8d8a0f5fb..000000000 --- a/src/tag_file.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_file.h" -#include "uri.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "input_stream.h" - -#include <assert.h> -#include <unistd.h> /* for SEEK_SET */ - -bool -tag_file_scan(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - assert(path_fs != NULL); - assert(handler != NULL); - - /* check if there's a suffix and a plugin */ - - const char *suffix = uri_get_suffix(path_fs); - if (suffix == NULL) - return false; - - const struct decoder_plugin *plugin = - decoder_plugin_from_suffix(suffix, NULL); - if (plugin == NULL) - return false; - - struct input_stream *is = NULL; - GMutex *mutex = NULL; - GCond *cond = NULL; - - do { - /* load file tag */ - if (decoder_plugin_scan_file(plugin, path_fs, - handler, handler_ctx)) - break; - - /* fall back to stream tag */ - if (plugin->scan_stream != NULL) { - /* open the input_stream (if not already - open) */ - if (is == NULL) { - mutex = g_mutex_new(); - cond = g_cond_new(); - is = input_stream_open(path_fs, mutex, cond, - NULL); - } - - /* now try the stream_tag() method */ - if (is != NULL) { - if (decoder_plugin_scan_stream(plugin, is, - handler, - handler_ctx)) - break; - - input_stream_lock_seek(is, 0, SEEK_SET, NULL); - } - } - - plugin = decoder_plugin_from_suffix(suffix, plugin); - } while (plugin != NULL); - - if (is != NULL) { - input_stream_close(is); - g_cond_free(cond); - g_mutex_free(mutex); - } - - return plugin != NULL; -} diff --git a/src/tag_file.h b/src/tag_file.h deleted file mode 100644 index 8cf1af3cb..000000000 --- a/src/tag_file.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_FILE_H -#define MPD_TAG_FILE_H - -#include "check.h" - -#include <stdbool.h> - -struct tag_handler; - -/** - * Scan the tags of a song file. Invokes matching decoder plugins, - * but does not invoke the special "APE" and "ID3" scanners. - */ -bool -tag_file_scan(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/tag_handler.c b/src/tag_handler.c deleted file mode 100644 index 316715e87..000000000 --- a/src/tag_handler.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_handler.h" - -#include <glib.h> - -static void -add_tag_duration(unsigned seconds, void *ctx) -{ - struct tag *tag = ctx; - - tag->time = seconds; -} - -static void -add_tag_tag(enum tag_type type, const char *value, void *ctx) -{ - struct tag *tag = ctx; - - tag_add_item(tag, type, value); -} - -const struct tag_handler add_tag_handler = { - .duration = add_tag_duration, - .tag = add_tag_tag, -}; - -static void -full_tag_pair(const char *name, G_GNUC_UNUSED const char *value, void *ctx) -{ - struct tag *tag = ctx; - - if (g_ascii_strcasecmp(name, "cuesheet") == 0) - tag->has_playlist = true; -} - -const struct tag_handler full_tag_handler = { - .duration = add_tag_duration, - .tag = add_tag_tag, - .pair = full_tag_pair, -}; - diff --git a/src/tag_handler.h b/src/tag_handler.h deleted file mode 100644 index 7f15c2a48..000000000 --- a/src/tag_handler.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_HANDLER_H -#define MPD_TAG_HANDLER_H - -#include "check.h" -#include "tag.h" - -#include <assert.h> - -/** - * A callback table for receiving metadata of a song. - */ -struct tag_handler { - /** - * Declare the duration of a song, in seconds. Do not call - * this when the duration could not be determined, because - * there is no magic value for "unknown duration". - */ - void (*duration)(unsigned seconds, void *ctx); - - /** - * A tag has been read. - * - * @param the value of the tag; the pointer will become - * invalid after returning - */ - void (*tag)(enum tag_type type, const char *value, void *ctx); - - /** - * A name-value pair has been read. It is the codec specific - * representation of tags. - */ - void (*pair)(const char *key, const char *value, void *ctx); -}; - -static inline void -tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx, - unsigned seconds) -{ - assert(handler != NULL); - - if (handler->duration != NULL) - handler->duration(seconds, ctx); -} - -static inline void -tag_handler_invoke_tag(const struct tag_handler *handler, void *ctx, - enum tag_type type, const char *value) -{ - assert(handler != NULL); - assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES); - assert(value != NULL); - - if (handler->tag != NULL) - handler->tag(type, value, ctx); -} - -static inline void -tag_handler_invoke_pair(const struct tag_handler *handler, void *ctx, - const char *name, const char *value) -{ - assert(handler != NULL); - assert(name != NULL); - assert(value != NULL); - - if (handler->pair != NULL) - handler->pair(name, value, ctx); -} - -/** - * This #tag_handler implementation adds tag values to a #tag object - * (casted from the context pointer). - */ -extern const struct tag_handler add_tag_handler; - -/** - * This #tag_handler implementation adds tag values to a #tag object - * (casted from the context pointer), and supports the has_playlist - * attribute. - */ -extern const struct tag_handler full_tag_handler; - -#endif diff --git a/src/tag_id3.c b/src/tag_id3.c deleted file mode 100644 index 0971829f0..000000000 --- a/src/tag_id3.c +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_id3.h" -#include "tag_handler.h" -#include "tag_table.h" -#include "tag.h" -#include "riff.h" -#include "aiff.h" -#include "conf.h" - -#include <glib.h> -#include <id3tag.h> - -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "id3" - -# ifndef ID3_FRAME_COMPOSER -# define ID3_FRAME_COMPOSER "TCOM" -# 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" /* 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) -{ - union id3_field *field; - const id3_ucs4_t *ucs4; - - field = id3_frame_field(frame, i); - if (field == NULL) - return NULL; - - ucs4 = id3_field_getstring(field); - if (ucs4 == NULL) - return NULL; - - return id3_ucs4_utf8duplicate(ucs4); -} - -/* This will try to convert a string to utf-8, - */ -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; - - /* use encoding field here? */ - if (is_id3v1 && - (encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) { - isostr = id3_ucs4_latin1duplicate(ucs4); - if (G_UNLIKELY(!isostr)) { - return NULL; - } - - utf8 = (id3_utf8_t *) - g_convert_with_fallback((const char*)isostr, -1, - "utf-8", encoding, - NULL, NULL, NULL, NULL); - if (utf8 == NULL) { - g_debug("Unable to convert %s string to UTF-8: '%s'", - encoding, isostr); - g_free(isostr); - return NULL; - } - g_free(isostr); - } else { - utf8 = id3_ucs4_utf8duplicate(ucs4); - if (G_UNLIKELY(!utf8)) { - return NULL; - } - } - - utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8)); - g_free(utf8); - - return utf8_stripped; -} - -/** - * Import a "Text information frame" (ID3v2.4.0 section 4.2). It - * contains 2 fields: - * - * - encoding - * - string list - */ -static void -tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, - enum tag_type type, - const struct tag_handler *handler, void *handler_ctx) -{ - id3_ucs4_t const *ucs4; - id3_utf8_t *utf8; - union id3_field const *field; - unsigned int nstrings, i; - - if (frame->nfields != 2) - return; - - /* check the encoding field */ - - field = id3_frame_field(frame, 0); - if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING) - return; - - /* 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_handler_invoke_tag(handler, handler_ctx, - type, (const char *)utf8); - g_free(utf8); - } -} - -/** - * 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 id3_tag *tag, const char *id, enum tag_type type, - const struct tag_handler *handler, void *handler_ctx) -{ - const struct id3_frame *frame; - for (unsigned i = 0; - (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i) - tag_id3_import_text_frame(tag, frame, type, - handler, handler_ctx); -} - -/** - * 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 id3_tag *tag, - const struct id3_frame *frame, enum tag_type type, - const struct tag_handler *handler, - void *handler_ctx) -{ - id3_ucs4_t const *ucs4; - id3_utf8_t *utf8; - 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_handler_invoke_tag(handler, handler_ctx, type, (const 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 id3_tag *tag, const char *id, enum tag_type type, - const struct tag_handler *handler, void *handler_ctx) -{ - const struct id3_frame *frame; - for (unsigned i = 0; - (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i) - tag_id3_import_comment_frame(tag, frame, type, - handler, handler_ctx); -} - -/** - * Parse a TXXX name, and convert it to a tag_type enum value. - * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood. - */ -static enum tag_type -tag_id3_parse_txxx_name(const char *name) -{ - static const struct tag_table txxx_tags[] = { - { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT }, - { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID }, - { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID }, - { "MusicBrainz Album Artist Id", - TAG_MUSICBRAINZ_ALBUMARTISTID }, - { "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID }, - { NULL, TAG_NUM_OF_ITEM_TYPES } - }; - - return tag_table_lookup(txxx_tags, name); -} - -/** - * Import all known MusicBrainz tags from TXXX frames. - */ -static void -tag_id3_import_musicbrainz(struct id3_tag *id3_tag, - const struct tag_handler *handler, - void *handler_ctx) -{ - for (unsigned i = 0;; ++i) { - const struct id3_frame *frame; - id3_utf8_t *name, *value; - enum tag_type type; - - frame = id3_tag_findframe(id3_tag, "TXXX", i); - if (frame == NULL) - break; - - name = tag_id3_getstring(frame, 1); - if (name == NULL) - continue; - - value = tag_id3_getstring(frame, 2); - if (value == NULL) - continue; - - tag_handler_invoke_pair(handler, handler_ctx, - (const char *)name, - (const char *)value); - - type = tag_id3_parse_txxx_name((const char*)name); - free(name); - - if (type != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, handler_ctx, - type, (const char*)value); - - free(value); - } -} - -/** - * Imports the MusicBrainz TrackId from the UFID tag. - */ -static void -tag_id3_import_ufid(struct id3_tag *id3_tag, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0;; ++i) { - const struct id3_frame *frame; - union id3_field *field; - const id3_latin1_t *name; - const id3_byte_t *value; - id3_length_t length; - - frame = id3_tag_findframe(id3_tag, "UFID", i); - if (frame == NULL) - break; - - field = id3_frame_field(frame, 0); - if (field == NULL) - continue; - - name = id3_field_getlatin1(field); - if (name == NULL || - strcmp((const char *)name, "http://musicbrainz.org") != 0) - continue; - - field = id3_frame_field(frame, 1); - if (field == NULL) - continue; - - value = id3_field_getbinarydata(field, &length); - if (value == NULL || length == 0) - continue; - - char *p = g_strndup((const char *)value, length); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_MUSICBRAINZ_TRACKID, p); - g_free(p); - } -} - -static void -scan_id3_tag(struct id3_tag *tag, - const struct tag_handler *handler, void *handler_ctx) -{ - tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST, - TAG_ALBUM_ARTIST, handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT, - TAG_ARTIST_SORT, handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT, - TAG_ALBUM_ARTIST_SORT, handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER, - handler, handler_ctx); - tag_id3_import_text(tag, "TPE3", TAG_PERFORMER, - handler, handler_ctx); - tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx); - tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT, - handler, handler_ctx); - tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC, - handler, handler_ctx); - - tag_id3_import_musicbrainz(tag, handler, handler_ctx); - tag_id3_import_ufid(tag, handler, handler_ctx); -} - -struct tag *tag_id3_import(struct id3_tag * tag) -{ - struct tag *ret = tag_new(); - - scan_id3_tag(tag, &add_tag_handler, ret); - - if (tag_is_empty(ret)) { - tag_free(ret); - ret = NULL; - } - - return ret; -} - -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 -get_id3v2_footer_size(FILE *stream, long offset, int whence) -{ - id3_byte_t buf[ID3_TAG_QUERYSIZE]; - int bufsize; - - 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 * -tag_id3_read(FILE *stream, long offset, int whence) -{ - struct id3_tag *tag; - 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 */ - 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 */ - 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. */ - tag_buffer = g_malloc(tag_size); - if (!tag_buffer) return NULL; - - 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(tag_buffer, tag_buffer_size); - - g_free(tag_buffer); - - return tag; -} - -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 = tag_id3_read(stream, 0, SEEK_SET); - if (!tag) { - return NULL; - } else if (tag_is_id3v1(tag)) { - /* id3v1 tags don't belong here */ - id3_tag_delete(tag); - return NULL; - } - - /* We have an id3v2 tag, so let's look for SEEK frames */ - while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { - /* Found a SEEK frame, get it's value */ - seek = id3_field_getint(id3_frame_field(frame, 0)); - if (seek < 0) - break; - - /* Get the tag specified by the SEEK frame */ - seektag = tag_id3_read(stream, seek, SEEK_CUR); - if (!seektag || tag_is_id3v1(seektag)) - break; - - /* Replace the old tag with the new one */ - id3_tag_delete(tag); - tag = seektag; - } - - return tag; -} - -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 = tag_id3_read(stream, -128, SEEK_END); - - /* Get the id3v2 tag size from the footer (located before v1tag) */ - 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 = tag_id3_read(stream, tagsize, SEEK_CUR); - if (!tag) - return v1tag; - - /* We have an id3v2 tag, so ditch v1tag */ - id3_tag_delete(v1tag); - - return tag; -} - -static struct id3_tag * -tag_id3_riff_aiff_load(FILE *file) -{ - size_t size; - void *buffer; - size_t ret; - struct id3_tag *tag; - - size = riff_seek_id3(file); - if (size == 0) - size = aiff_seek_id3(file); - if (size == 0) - return NULL; - - if (size > 4 * 1024 * 1024) - /* too large, don't allocate so much memory */ - return NULL; - - buffer = g_malloc(size); - ret = fread(buffer, size, 1, file); - if (ret != 1) { - g_warning("Failed to read RIFF chunk"); - g_free(buffer); - return NULL; - } - - tag = id3_tag_parse(buffer, size); - g_free(buffer); - return tag; -} - -struct id3_tag * -tag_id3_load(const char *path_fs, GError **error_r) -{ - FILE *file = fopen(path_fs, "rb"); - if (file == NULL) { - g_set_error(error_r, g_file_error_quark(), errno, - "Failed to open file %s: %s", - path_fs, g_strerror(errno)); - return NULL; - } - - struct id3_tag *tag = tag_id3_find_from_beginning(file); - if (tag == NULL) { - tag = tag_id3_riff_aiff_load(file); - if (tag == NULL) - tag = tag_id3_find_from_end(file); - } - - fclose(file); - return tag; -} - -bool -tag_id3_scan(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - GError *error = NULL; - struct id3_tag *tag = tag_id3_load(path_fs, &error); - if (tag == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return false; - } - - scan_id3_tag(tag, handler, handler_ctx); - id3_tag_delete(tag); - return true; -} diff --git a/src/tag_id3.h b/src/tag_id3.h deleted file mode 100644 index 049c53ad9..000000000 --- a/src/tag_id3.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_ID3_H -#define MPD_TAG_ID3_H - -#include "check.h" - -#include <glib.h> - -#include <stdbool.h> - -struct tag_handler; -struct tag; - -#ifdef HAVE_ID3TAG - -bool -tag_id3_scan(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx); - -struct id3_tag; -struct tag *tag_id3_import(struct id3_tag *); - -/** - * Loads the ID3 tags from the file into a libid3tag object. The - * return value must be freed with id3_tag_delete(). - * - * @return NULL on error or if no ID3 tag was found in the file (no - * GError will be set) - */ -struct id3_tag * -tag_id3_load(const char *path_fs, GError **error_r); - -#else - -static inline bool -tag_id3_scan(G_GNUC_UNUSED const char *path_fs, - G_GNUC_UNUSED const struct tag_handler *handler, - G_GNUC_UNUSED void *handler_ctx) -{ - return false; -} - -#endif - -#endif diff --git a/src/tag_internal.h b/src/tag_internal.h deleted file mode 100644 index af05cc6b6..000000000 --- a/src/tag_internal.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_INTERNAL_H -#define MPD_TAG_INTERNAL_H - -#include "tag.h" - -#include <stdbool.h> - -extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; - -#endif diff --git a/src/tag_pool.c b/src/tag_pool.c deleted file mode 100644 index eabf3e369..000000000 --- a/src/tag_pool.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_pool.h" - -#include <assert.h> - -GMutex *tag_pool_lock = NULL; - -#define NUM_SLOTS 4096 - -struct slot { - struct slot *next; - unsigned char ref; - struct tag_item item; -} mpd_packed; - -static struct slot *slots[NUM_SLOTS]; - -static inline unsigned -calc_hash_n(enum tag_type type, const char *p, size_t length) -{ - unsigned hash = 5381; - - assert(p != NULL); - - while (length-- > 0) - hash = (hash << 5) + hash + *p++; - - return hash ^ type; -} - -static inline unsigned -calc_hash(enum tag_type type, const char *p) -{ - unsigned hash = 5381; - - assert(p != NULL); - - while (*p != 0) - hash = (hash << 5) + hash + *p++; - - return hash ^ type; -} - -static inline struct slot * -tag_item_to_slot(struct tag_item *item) -{ - return (struct slot*)(((char*)item) - offsetof(struct slot, item)); -} - -static struct slot *slot_alloc(struct slot *next, - enum tag_type type, - const char *value, int length) -{ - struct slot *slot; - - slot = g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); - slot->next = next; - slot->ref = 1; - slot->item.type = type; - memcpy(slot->item.value, value, length); - slot->item.value[length] = 0; - return slot; -} - -void tag_pool_init(void) -{ - g_assert(tag_pool_lock == NULL); - tag_pool_lock = g_mutex_new(); -} - -void tag_pool_deinit(void) -{ - g_assert(tag_pool_lock != NULL); - g_mutex_free(tag_pool_lock); - tag_pool_lock = NULL; -} - -struct tag_item * -tag_pool_get_item(enum tag_type type, const char *value, size_t length) -{ - struct slot **slot_p, *slot; - - slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; - for (slot = *slot_p; slot != NULL; slot = slot->next) { - if (slot->item.type == type && - length == strlen(slot->item.value) && - memcmp(value, slot->item.value, length) == 0 && - slot->ref < 0xff) { - assert(slot->ref > 0); - ++slot->ref; - return &slot->item; - } - } - - slot = slot_alloc(*slot_p, type, value, length); - *slot_p = slot; - return &slot->item; -} - -struct tag_item *tag_pool_dup_item(struct tag_item *item) -{ - struct slot *slot = tag_item_to_slot(item); - - assert(slot->ref > 0); - - if (slot->ref < 0xff) { - ++slot->ref; - return item; - } else { - /* the reference counter overflows above 0xff; - duplicate the item, and start with 1 */ - size_t length = strlen(item->value); - struct slot **slot_p = - &slots[calc_hash_n(item->type, item->value, - length) % NUM_SLOTS]; - slot = slot_alloc(*slot_p, item->type, - item->value, strlen(item->value)); - *slot_p = slot; - return &slot->item; - } -} - -void tag_pool_put_item(struct tag_item *item) -{ - struct slot **slot_p, *slot; - - slot = tag_item_to_slot(item); - assert(slot->ref > 0); - --slot->ref; - - if (slot->ref > 0) - return; - - for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; - *slot_p != slot; - slot_p = &(*slot_p)->next) { - assert(*slot_p != NULL); - } - - *slot_p = slot->next; - g_free(slot); -} diff --git a/src/tag_pool.h b/src/tag_pool.h deleted file mode 100644 index a96c00d85..000000000 --- a/src/tag_pool.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_POOL_H -#define MPD_TAG_POOL_H - -#include "tag.h" - -#include <glib.h> - -extern GMutex *tag_pool_lock; - -struct tag_item; - -void tag_pool_init(void); - -void tag_pool_deinit(void); - -struct tag_item * -tag_pool_get_item(enum tag_type type, const char *value, size_t length); - -struct tag_item *tag_pool_dup_item(struct tag_item *item); - -void tag_pool_put_item(struct tag_item *item); - -#endif diff --git a/src/tag_print.c b/src/tag_print.c deleted file mode 100644 index 9a46b247a..000000000 --- a/src/tag_print.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_print.h" -#include "tag.h" -#include "tag_internal.h" -#include "client.h" -#include "song.h" - -void tag_print_types(struct client *client) -{ - int i; - - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { - if (!ignore_tag_items[i]) - client_printf(client, "tagtype: %s\n", - tag_item_names[i]); - } -} - -void tag_print(struct client *client, const struct tag *tag) -{ - if (tag->time >= 0) - client_printf(client, SONG_TIME "%i\n", tag->time); - - for (unsigned i = 0; i < tag->num_items; i++) { - client_printf(client, "%s: %s\n", - tag_item_names[tag->items[i]->type], - tag->items[i]->value); - } -} diff --git a/src/tag_print.h b/src/tag_print.h deleted file mode 100644 index b9eeeaecf..000000000 --- a/src/tag_print.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_PRINT_H -#define MPD_TAG_PRINT_H - -struct tag; -struct client; - -void tag_print_types(struct client *client); - -void tag_print(struct client *client, const struct tag *tag); - -#endif diff --git a/src/tag_rva2.c b/src/tag_rva2.c deleted file mode 100644 index 9250311b9..000000000 --- a/src/tag_rva2.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_rva2.h" -#include "replay_gain_info.h" - -#include <stdint.h> -#include <string.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, const id3_latin1_t *id) -{ - if (data->type != CHANNEL_MASTER_VOLUME) - return false; - - float volume_adjustment = rva2_float_volume_adjustment(data); - - if (strcmp((const char *)id, "album") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment; - } else if (strcmp((const char *)id, "track") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment; - } else { - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment; - } - - return true; -} - -static bool -rva2_apply_frame(struct replay_gain_info *replay_gain_info, - const struct id3_frame *frame) -{ - const id3_latin1_t *id = id3_field_getlatin1(id3_frame_field(frame, 0)); - id3_length_t length; - const id3_byte_t *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, id)) - return true; - - data += 4 + peak_bytes; - length -= 4 + peak_bytes; - } - - return false; -} - -bool -tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info) -{ - bool found = false; - - /* Loop through all RVA2 frames as some programs (e.g. mp3gain) store - track and album gain in separate tags */ - const struct id3_frame *frame; - for (unsigned i = 0; - (frame = id3_tag_findframe(tag, "RVA2", i)) != NULL; - ++i) - if (rva2_apply_frame(replay_gain_info, frame)) - found = true; - - return found; -} diff --git a/src/tag_rva2.h b/src/tag_rva2.h deleted file mode 100644 index 8aac2fe9f..000000000 --- a/src/tag_rva2.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_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 deleted file mode 100644 index 2fdaef56c..000000000 --- a/src/tag_save.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_save.h" -#include "tag.h" -#include "tag_internal.h" -#include "song.h" - -void tag_save(FILE *file, const struct tag *tag) -{ - if (tag->time >= 0) - fprintf(file, SONG_TIME "%i\n", tag->time); - - if (tag->has_playlist) - fprintf(file, "Playlist: yes\n"); - - for (unsigned i = 0; i < tag->num_items; i++) - fprintf(file, "%s: %s\n", - tag_item_names[tag->items[i]->type], - tag->items[i]->value); -} diff --git a/src/tag_save.h b/src/tag_save.h deleted file mode 100644 index 9f6a580c8..000000000 --- a/src/tag_save.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_SAVE_H -#define MPD_TAG_SAVE_H - -#include <stdio.h> - -struct tag; - -void tag_save(FILE *file, const struct tag *tag); - -#endif diff --git a/src/tag_table.h b/src/tag_table.h deleted file mode 100644 index d87d4869a..000000000 --- a/src/tag_table.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TAG_TABLE_H -#define MPD_TAG_TABLE_H - -#include "tag.h" - -#include <glib.h> - -struct tag_table { - const char *name; - - enum tag_type type; -}; - -/** - * Looks up a string in a tag translation table (case sensitive). - * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found - * in the table. - */ -G_GNUC_PURE -static inline enum tag_type -tag_table_lookup(const struct tag_table *table, const char *name) -{ - for (; table->name != NULL; ++table) - if (strcmp(name, table->name) == 0) - return table->type; - - return TAG_NUM_OF_ITEM_TYPES; -} - -/** - * Looks up a string in a tag translation table (case insensitive). - * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found - * in the table. - */ -G_GNUC_PURE -static inline enum tag_type -tag_table_lookup_i(const struct tag_table *table, const char *name) -{ - for (; table->name != NULL; ++table) - if (g_ascii_strcasecmp(name, table->name) == 0) - return table->type; - - return TAG_NUM_OF_ITEM_TYPES; -} - -#endif diff --git a/src/text_file.c b/src/text_file.c deleted file mode 100644 index 3674e5ce2..000000000 --- a/src/text_file.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 deleted file mode 100644 index 9dd810943..000000000 --- a/src/text_file.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index 4a858fc85..000000000 --- a/src/text_input_stream.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "text_input_stream.h" -#include "input_stream.h" -#include "fifo_buffer.h" - -#include <glib.h> - -#include <assert.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 && length >= 2) { - /* reserve one byte for the null terminator if - the last line is not terminated by a - newline character */ - --length; - - nbytes = input_stream_lock_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; - } - } else - nbytes = 0; - - src = fifo_buffer_read(tis->buffer, &length); - if (src == NULL) - return NULL; - - p = memchr(src, '\n', length); - if (p == NULL && nbytes == 0) { - /* end of file (or line too long): terminate - the current line */ - dest = fifo_buffer_write(tis->buffer, &nbytes); - assert(dest != NULL); - *(char *)dest = '\n'; - fifo_buffer_append(tis->buffer, 1); - } - } 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 deleted file mode 100644 index 9b3245689..000000000 --- a/src/text_input_stream.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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/thread/Cond.hxx b/src/thread/Cond.hxx new file mode 100644 index 000000000..bbaabddc2 --- /dev/null +++ b/src/thread/Cond.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_THREAD_COND_HXX +#define MPD_THREAD_COND_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::cond implementation */ + +#include "WindowsCond.hxx" +typedef WindowsCond Cond; + +#else + +#include "PosixCond.hxx" +typedef PosixCond Cond; + +#endif + +#endif diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx new file mode 100644 index 000000000..8bc05b8f5 --- /dev/null +++ b/src/thread/CriticalSection.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_THREAD_CRITICAL_SECTION_HXX +#define MPD_THREAD_CRITICAL_SECTION_HXX + +#include <windows.h> + +/** + * Wrapper for a CRITICAL_SECTION, backend for the Mutex class. + */ +class CriticalSection { + friend class WindowsCond; + + CRITICAL_SECTION critical_section; + +public: + CriticalSection() { + ::InitializeCriticalSection(&critical_section); + } + + ~CriticalSection() { + ::DeleteCriticalSection(&critical_section); + } + + CriticalSection(const CriticalSection &other) = delete; + CriticalSection &operator=(const CriticalSection &other) = delete; + + void lock() { + ::EnterCriticalSection(&critical_section); + }; + + bool try_lock() { + return ::TryEnterCriticalSection(&critical_section) != 0; + }; + + void unlock() { + ::LeaveCriticalSection(&critical_section); + } +}; + +#endif diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx new file mode 100644 index 000000000..9ab08e9fd --- /dev/null +++ b/src/thread/GLibCond.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_THREAD_GLIB_COND_HXX +#define MPD_THREAD_GLIB_COND_HXX + +#include "GLibMutex.hxx" + +/** + * A wrapper for GCond. + */ +class GLibCond { +#if GLIB_CHECK_VERSION(2,32,0) + GCond cond; +#else + GCond *cond; +#endif + +public: + GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_init(&cond); +#else + cond = g_cond_new(); +#endif + } + + ~GLibCond() { +#if GLIB_CHECK_VERSION(2,32,0) + g_cond_clear(&cond); +#else + g_cond_free(cond); +#endif + } + + GLibCond(const GLibCond &other) = delete; + GLibCond &operator=(const GLibCond &other) = delete; + +private: + GCond *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &cond; +#else + return cond; +#endif + } + +public: + void signal() { + g_cond_signal(GetNative()); + } + + void broadcast() { + g_cond_broadcast(GetNative()); + } + + void wait(GLibMutex &mutex) { + g_cond_wait(GetNative(), mutex.GetNative()); + } +}; + +#endif diff --git a/src/thread/GLibMutex.hxx b/src/thread/GLibMutex.hxx new file mode 100644 index 000000000..2c666c1ea --- /dev/null +++ b/src/thread/GLibMutex.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_THREAD_GLIB_MUTEX_HXX +#define MPD_THREAD_GLIB_MUTEX_HXX + +#include <glib.h> + +/** + * A wrapper for GMutex. + */ +class GLibMutex { + friend class GLibCond; + +#if GLIB_CHECK_VERSION(2,32,0) + GMutex mutex; +#else + GMutex *mutex; +#endif + +public: + GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_init(&mutex); +#else + mutex = g_mutex_new(); +#endif + } + + ~GLibMutex() { +#if GLIB_CHECK_VERSION(2,32,0) + g_mutex_clear(&mutex); +#else + g_mutex_free(mutex); +#endif + } + + GLibMutex(const GLibMutex &other) = delete; + GLibMutex &operator=(const GLibMutex &other) = delete; + +private: + GMutex *GetNative() { +#if GLIB_CHECK_VERSION(2,32,0) + return &mutex; +#else + return mutex; +#endif + } + +public: + void lock() { + g_mutex_lock(GetNative()); + } + + bool try_lock() { + return g_mutex_trylock(GetNative()); + } + + void unlock() { + g_mutex_lock(GetNative()); + } +}; + +#endif diff --git a/src/thread/Id.hxx b/src/thread/Id.hxx new file mode 100644 index 000000000..3d0dfe311 --- /dev/null +++ b/src/thread/Id.hxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_THREAD_ID_HXX +#define MPD_THREAD_ID_HXX + +#include "gcc.h" + +#ifdef WIN32 +#include <windows.h> +#else +#include <pthread.h> +#endif + +/** + * A low-level identification for a thread. Designed to work with + * existing threads, such as the main thread. Mostly useful for + * debugging code. + */ +class ThreadId { +#ifdef WIN32 + DWORD id; +#else + pthread_t id; +#endif + +public: + /** + * No initialisation. + */ + ThreadId() = default; + +#ifdef WIN32 + constexpr ThreadId(DWORD _id):id(_id) {} +#else + constexpr ThreadId(pthread_t _id):id(_id) {} +#endif + + gcc_const + static ThreadId Null() { +#ifdef WIN32 + return 0; +#else + static ThreadId null; + return null; +#endif + } + + gcc_pure + bool IsNull() const { + return *this == Null(); + } + + /** + * Return the current thread's id . + */ + gcc_pure + static const ThreadId GetCurrent() { +#ifdef WIN32 + return ::GetCurrentThreadId(); +#else + return ::pthread_self(); +#endif + } + + gcc_pure + bool operator==(const ThreadId &other) const { +#ifdef WIN32 + return id == other.id; +#else + return ::pthread_equal(id, other.id); +#endif + } + + /** + * Check if this thread is the current thread. + */ + bool IsInside() const { + return *this == GetCurrent(); + } +}; + +#endif diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx new file mode 100644 index 000000000..675af74b1 --- /dev/null +++ b/src/thread/Mutex.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_THREAD_MUTEX_HXX +#define MPD_THREAD_MUTEX_HXX + +#ifdef WIN32 + +/* mingw-w64 4.6.3 lacks a std::mutex implementation */ + +#include "CriticalSection.hxx" +typedef CriticalSection Mutex; + +#else + +#include "PosixMutex.hxx" + +typedef PosixMutex Mutex; + +#endif + +class ScopeLock { + Mutex &mutex; + +public: + ScopeLock(Mutex &_mutex):mutex(_mutex) { + mutex.lock(); + }; + + ~ScopeLock() { + mutex.unlock(); + }; + + ScopeLock(const ScopeLock &other) = delete; + ScopeLock &operator=(const ScopeLock &other) = delete; +}; + +#endif diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx new file mode 100644 index 000000000..6f98d3ad0 --- /dev/null +++ b/src/thread/PosixCond.hxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_THREAD_POSIX_COND_HXX +#define MPD_THREAD_POSIX_COND_HXX + +#include "PosixMutex.hxx" + +#include <sys/time.h> + +/** + * Low-level wrapper for a pthread_cond_t. + */ +class PosixCond { + pthread_cond_t cond; + +public: + constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {} + + PosixCond(const PosixCond &other) = delete; + PosixCond &operator=(const PosixCond &other) = delete; + + void signal() { + pthread_cond_signal(&cond); + } + + void broadcast() { + pthread_cond_broadcast(&cond); + } + + void wait(PosixMutex &mutex) { + pthread_cond_wait(&cond, &mutex.mutex); + } + + bool timed_wait(PosixMutex &mutex, unsigned timeout_ms) { + struct timeval now; + gettimeofday(&now, nullptr); + + struct timespec ts; + ts.tv_sec = now.tv_sec + timeout_ms / 1000; + ts.tv_nsec = (now.tv_usec + (timeout_ms % 1000) * 1000) * 1000; + + return pthread_cond_timedwait(&cond, &mutex.mutex, &ts) == 0; + } +}; + +#endif diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx new file mode 100644 index 000000000..d50764af4 --- /dev/null +++ b/src/thread/PosixMutex.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_THREAD_POSIX_MUTEX_HXX +#define MPD_THREAD_POSIX_MUTEX_HXX + +#include <pthread.h> + +/** + * Low-level wrapper for a pthread_mutex_t. + */ +class PosixMutex { + friend class PosixCond; + + pthread_mutex_t mutex; + +public: + constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {} + + PosixMutex(const PosixMutex &other) = delete; + PosixMutex &operator=(const PosixMutex &other) = delete; + + void lock() { + pthread_mutex_lock(&mutex); + } + + bool try_lock() { + return pthread_mutex_trylock(&mutex) == 0; + } + + void unlock() { + pthread_mutex_unlock(&mutex); + } +}; + +#endif diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx new file mode 100644 index 000000000..c05bc05b2 --- /dev/null +++ b/src/thread/WindowsCond.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009-2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_THREAD_WINDOWS_COND_HXX +#define MPD_THREAD_WINDOWS_COND_HXX + +#include "CriticalSection.hxx" + +/** + * Wrapper for a CONDITION_VARIABLE, backend for the Cond class. + */ +class WindowsCond { + CONDITION_VARIABLE cond; + +public: + WindowsCond() { + InitializeConditionVariable(&cond); + } + + WindowsCond(const WindowsCond &other) = delete; + WindowsCond &operator=(const WindowsCond &other) = delete; + + void signal() { + WakeConditionVariable(&cond); + } + + void broadcast() { + WakeAllConditionVariable(&cond); + } + + bool timed_wait(CriticalSection &mutex, DWORD timeout_ms) { + return SleepConditionVariableCS(&cond, &mutex.critical_section, + timeout_ms); + } + + void wait(CriticalSection &mutex) { + timed_wait(mutex, INFINITE); + } +}; + +#endif diff --git a/src/timer.c b/src/timer.c deleted file mode 100644 index 9a3228465..000000000 --- a/src/timer.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "timer.h" -#include "audio_format.h" -#include "clock.h" - -#include <glib.h> - -#include <assert.h> -#include <limits.h> -#include <stddef.h> - -struct timer *timer_new(const struct audio_format *af) -{ - struct timer *timer = g_new(struct timer, 1); - timer->time = 0; // us - timer->started = 0; // false - timer->rate = af->sample_rate * audio_format_frame_size(af); // samples per second - - return timer; -} - -void timer_free(struct timer *timer) -{ - g_free(timer); -} - -void timer_start(struct timer *timer) -{ - timer->time = monotonic_clock_us(); - timer->started = 1; -} - -void timer_reset(struct timer *timer) -{ - timer->time = 0; - timer->started = 0; -} - -void timer_add(struct timer *timer, int size) -{ - assert(timer->started); - - // (size samples) / (rate samples per second) = duration seconds - // duration seconds * 1000000 = duration us - timer->time += ((uint64_t)size * 1000000) / timer->rate; -} - -unsigned -timer_delay(const struct timer *timer) -{ - int64_t delay = (int64_t)(timer->time - monotonic_clock_us()) / 1000; - if (delay < 0) - return 0; - - if (delay > G_MAXINT) - delay = G_MAXINT; - - return delay; -} - -void timer_sync(struct timer *timer) -{ - int64_t sleep_duration; - - assert(timer->started); - - sleep_duration = timer->time - monotonic_clock_us(); - if (sleep_duration > 0) - g_usleep(sleep_duration); -} diff --git a/src/timer.h b/src/timer.h deleted file mode 100644 index 184881249..000000000 --- a/src/timer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TIMER_H -#define MPD_TIMER_H - -#include <stdint.h> - -struct audio_format; - -struct timer { - uint64_t time; - int started; - int rate; -}; - -struct timer *timer_new(const struct audio_format *af); - -void timer_free(struct timer *timer); - -void timer_start(struct timer *timer); - -void timer_reset(struct timer *timer); - -void timer_add(struct timer *timer, int size); - -/** - * Returns the number of milliseconds to sleep to get back to sync. - */ -unsigned -timer_delay(const struct timer *timer); - -void timer_sync(struct timer *timer); - -#endif diff --git a/src/tokenizer.c b/src/tokenizer.c deleted file mode 100644 index bbb34e100..000000000 --- a/src/tokenizer.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tokenizer.h" -#include "string_util.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 = strchug_fast(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 = strchug_fast(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 = strchug_fast(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 deleted file mode 100644 index d55eb3ca6..000000000 --- a/src/tokenizer.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_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 deleted file mode 100644 index 12eec40c4..000000000 --- a/src/update.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "update.h" -#include "update_queue.h" -#include "update_walk.h" -#include "update_remove.h" -#include "update.h" -#include "database.h" -#include "mapper.h" -#include "playlist.h" -#include "event_pipe.h" -#include "update.h" -#include "idle.h" -#include "stats.h" -#include "main.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <assert.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "update" - -static enum update_progress { - UPDATE_PROGRESS_IDLE = 0, - UPDATE_PROGRESS_RUNNING = 1, - UPDATE_PROGRESS_DONE = 2 -} progress; - -static bool modified; - -static GThread *update_thr; - -static const unsigned update_task_id_max = 1 << 15; - -static unsigned update_task_id; - -/* XXX this flag is passed to update_task() */ -static bool discard; - -unsigned -isUpdatingDB(void) -{ - return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; -} - -static void * update_task(void *_path) -{ - const char *path = _path; - - if (path != NULL && *path != 0) - g_debug("starting: %s", path); - else - g_debug("starting"); - - modified = update_walk(path, discard); - - if (modified || !db_exists()) { - GError *error = NULL; - if (!db_save(&error)) { - g_warning("Failed to save database: %s", - error->message); - g_error_free(error); - } - } - - if (path != NULL && *path != 0) - g_debug("finished: %s", path); - else - g_debug("finished"); - g_free(_path); - - progress = UPDATE_PROGRESS_DONE; - event_pipe_emit(PIPE_EVENT_UPDATE); - return NULL; -} - -static void -spawn_update_task(const char *path) -{ - GError *e = NULL; - - assert(g_thread_self() == main_task); - - progress = UPDATE_PROGRESS_RUNNING; - modified = false; - - 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 -update_enqueue(const char *path, bool _discard) -{ - assert(g_thread_self() == main_task); - - if (!mapper_has_music_directory()) - return 0; - - if (progress != UPDATE_PROGRESS_IDLE) { - unsigned next_task_id = - update_queue_push(path, discard, update_task_id); - if (next_task_id == 0) - return 0; - - return next_task_id > update_task_id_max ? 1 : next_task_id; - } - - discard = _discard; - spawn_update_task(path); - - idle_add(IDLE_UPDATE); - - return update_task_id; -} - -/** - * Called in the main thread after the database update is finished. - */ -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 */ - playlist_increment_version_all(&g_playlist); - idle_add(IDLE_DATABASE); - } - - path = update_queue_shift(&discard); - if (path != NULL) { - /* schedule the next path */ - spawn_update_task(path); - g_free(path); - } else { - progress = UPDATE_PROGRESS_IDLE; - - stats_update(); - } -} - -void update_global_init(void) -{ - event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event); - - update_remove_global_init(); - update_walk_global_init(); -} - -void update_global_finish(void) -{ - update_walk_global_finish(); - update_remove_global_finish(); -} diff --git a/src/update.h b/src/update.h deleted file mode 100644 index 3d586b694..000000000 --- a/src/update.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_H -#define MPD_UPDATE_H - -#include <stdbool.h> - -void update_global_init(void); - -void update_global_finish(void); - -unsigned -isUpdatingDB(void); - -/** - * 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 -update_enqueue(const char *path, bool discard); - -#endif diff --git a/src/update_archive.c b/src/update_archive.c deleted file mode 100644 index 3fb2bc18c..000000000 --- a/src/update_archive.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_archive.h" -#include "update_internal.h" -#include "db_lock.h" -#include "directory.h" -#include "song.h" -#include "mapper.h" -#include "archive_list.h" -#include "archive_plugin.h" - -#include <glib.h> - -#include <string.h> - -static void -update_archive_tree(struct directory *directory, char *name) -{ - char *tmp = strchr(name, '/'); - if (tmp) { - *tmp = 0; - //add dir is not there already - db_lock(); - struct directory *subdir = - directory_make_child(directory, name); - subdir->device = DEVICE_INARCHIVE; - db_unlock(); - //create directories first - update_archive_tree(subdir, tmp+1); - } else { - if (strlen(name) == 0) { - g_warning("archive returned directory only"); - return; - } - - //add file - db_lock(); - struct song *song = directory_get_song(directory, name); - db_unlock(); - if (song == NULL) { - song = song_file_load(name, directory); - if (song != NULL) { - db_lock(); - directory_add_song(directory, song); - db_unlock(); - - 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_file2(struct directory *parent, const char *name, - const struct stat *st, - const struct archive_plugin *plugin) -{ - db_lock(); - struct directory *directory = directory_get_child(parent, name); - db_unlock(); - - if (directory != NULL && directory->mtime == st->st_mtime && - !walk_discard) - /* MPD has already scanned the archive, and it hasn't - changed since - don't consider updating it */ - return; - - char *path_fs = map_directory_child_fs(parent, name); - - /* open archive */ - GError *error = NULL; - struct archive_file *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); - db_lock(); - directory = directory_new_child(parent, name); - /* mark this directory as archive (we use device for - this) */ - directory->device = DEVICE_INARCHIVE; - db_unlock(); - } - - directory->mtime = st->st_mtime; - - archive_file_scan_reset(file); - - char *filepath; - 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); -} - -bool -update_archive_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st) -{ -#ifdef ENABLE_ARCHIVE - const struct archive_plugin *plugin = - archive_plugin_from_suffix(suffix); - if (plugin == NULL) - return false; - - update_archive_file2(directory, name, st, plugin); - return true; -#else - (void)directory; - (void)name; - (void)suffix; - (void)st; - - return false; -#endif -} diff --git a/src/update_archive.h b/src/update_archive.h deleted file mode 100644 index 838697dd9..000000000 --- a/src/update_archive.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_ARCHIVE_H -#define MPD_UPDATE_ARCHIVE_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; -struct archive_plugin; - -#ifdef ENABLE_ARCHIVE - -bool -update_archive_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st); - -#else - -#include <glib.h> - -static inline bool -update_archive_file(G_GNUC_UNUSED struct directory *directory, - G_GNUC_UNUSED const char *name, - G_GNUC_UNUSED const char *suffix, - G_GNUC_UNUSED const struct stat *st) -{ - return false; -} - -#endif - -#endif diff --git a/src/update_container.c b/src/update_container.c deleted file mode 100644 index bda95dabe..000000000 --- a/src/update_container.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_container.h" -#include "update_internal.h" -#include "update_db.h" -#include "db_lock.h" -#include "directory.h" -#include "song.h" -#include "mapper.h" -#include "decoder_plugin.h" -#include "tag.h" -#include "tag_handler.h" - -#include <glib.h> - -/** - * Create the specified directory object if it does not exist already - * or if the #stat object indicates that it has been modified since - * the last update. Returns NULL when it exists already and is - * unmodified. - * - * The caller must lock the database. - */ -static struct directory * -make_directory_if_modified(struct directory *parent, const char *name, - const struct stat *st) -{ - struct directory *directory = directory_get_child(parent, name); - - // directory exists already - if (directory != NULL) { - if (directory->mtime == st->st_mtime && !walk_discard) { - /* not modified */ - db_unlock(); - return NULL; - } - - delete_directory(directory); - modified = true; - } - - directory = directory_make_child(parent, name); - directory->mtime = st->st_mtime; - return directory; -} - -bool -update_container_file(struct directory *directory, - const char *name, - const struct stat *st, - const struct decoder_plugin *plugin) -{ - if (plugin->container_scan == NULL) - return false; - - db_lock(); - struct directory *contdir = - make_directory_if_modified(directory, name, st); - if (contdir == NULL) { - /* not modified */ - db_unlock(); - return true; - } - - contdir->device = DEVICE_CONTAINER; - db_unlock(); - - char *const pathname = map_directory_child_fs(directory, name); - - char *vtrack; - unsigned int tnum = 0; - while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) { - struct song *song = song_file_new(vtrack, contdir); - - // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - char *child_path_fs = map_directory_child_fs(contdir, vtrack); - - song->tag = tag_new(); - decoder_plugin_scan_file(plugin, child_path_fs, - &add_tag_handler, song->tag); - g_free(child_path_fs); - - db_lock(); - directory_add_song(contdir, song); - db_unlock(); - - modified = true; - - g_message("added %s/%s", - directory_get_path(directory), vtrack); - g_free(vtrack); - } - - g_free(pathname); - - if (tnum == 1) { - db_lock(); - delete_directory(contdir); - db_unlock(); - return false; - } else - return true; -} diff --git a/src/update_container.h b/src/update_container.h deleted file mode 100644 index 7f42c80ca..000000000 --- a/src/update_container.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_CONTAINER_H -#define MPD_UPDATE_CONTAINER_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; -struct decoder_plugin; - -bool -update_container_file(struct directory *directory, - const char *name, - const struct stat *st, - const struct decoder_plugin *plugin); - -#endif diff --git a/src/update_db.c b/src/update_db.c deleted file mode 100644 index 8982a53e2..000000000 --- a/src/update_db.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_db.h" -#include "update_remove.h" -#include "directory.h" -#include "song.h" -#include "playlist_vector.h" -#include "db_lock.h" - -#include <glib.h> -#include <assert.h> - -void -delete_song(struct directory *dir, struct song *del) -{ - assert(del->parent == dir); - - /* first, prevent traversers in main task from getting this */ - directory_remove_song(dir, del); - - db_unlock(); /* temporary unlock, because update_remove_song() blocks */ - - /* now take it out of the playlist (in the main_task) */ - update_remove_song(del); - - /* finally, all possible references gone, free it */ - song_free(del); - - db_lock(); -} - -/** - * Recursively remove all sub directories and songs from a directory, - * leaving an empty directory. - * - * Caller must lock the #db_mutex. - */ -static void -clear_directory(struct directory *directory) -{ - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) - delete_directory(child); - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - assert(song->parent == directory); - delete_song(directory, song); - } -} - -void -delete_directory(struct directory *directory) -{ - assert(directory->parent != NULL); - - clear_directory(directory); - - directory_delete(directory); -} - -bool -delete_name_in(struct directory *parent, const char *name) -{ - bool modified = false; - - db_lock(); - struct directory *directory = directory_get_child(parent, name); - - if (directory != NULL) { - delete_directory(directory); - modified = true; - } - - struct song *song = directory_get_song(parent, name); - if (song != NULL) { - delete_song(parent, song); - modified = true; - } - - playlist_vector_remove(&parent->playlists, name); - - db_unlock(); - - return modified; -} diff --git a/src/update_db.h b/src/update_db.h deleted file mode 100644 index 0a9e46b05..000000000 --- a/src/update_db.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_DB_H -#define MPD_UPDATE_DB_H - -#include "check.h" - -#include <stdbool.h> - -struct directory; -struct song; - -/** - * Caller must lock the #db_mutex. - */ -void -delete_song(struct directory *parent, struct song *song); - -/** - * Recursively free a directory and all its contents. - * - * Caller must lock the #db_mutex. - */ -void -delete_directory(struct directory *directory); - -/** - * Caller must NOT lock the #db_mutex. - * - * @return true if the database was modified - */ -bool -delete_name_in(struct directory *parent, const char *name); - -#endif diff --git a/src/update_internal.h b/src/update_internal.h deleted file mode 100644 index 76ab7bed3..000000000 --- a/src/update_internal.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_INTERNAL_H -#define MPD_UPDATE_INTERNAL_H - -#include "check.h" - -#include <stdbool.h> - -extern bool walk_discard; -extern bool modified; - -#endif diff --git a/src/update_io.c b/src/update_io.c deleted file mode 100644 index c6a540a0f..000000000 --- a/src/update_io.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_io.h" -#include "mapper.h" -#include "directory.h" -#include "glib_compat.h" - -#include <glib.h> - -#include <errno.h> -#include <unistd.h> - -int -stat_directory(const struct directory *directory, struct stat *st) -{ - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return -1; - - int ret = stat(path_fs, st); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; -} - -int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st) -{ - char *path_fs = map_directory_child_fs(parent, name); - if (path_fs == NULL) - return -1; - - int ret = stat(path_fs, st); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; -} - -bool -directory_exists(const struct directory *directory) -{ - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - /* invalid path: cannot exist */ - return false; - - GFileTest test = directory->device == DEVICE_INARCHIVE || - directory->device == DEVICE_CONTAINER - ? G_FILE_TEST_IS_REGULAR - : G_FILE_TEST_IS_DIR; - - bool exists = g_file_test(path_fs, test); - g_free(path_fs); - - return exists; -} - -bool -directory_child_is_regular(const struct directory *directory, - const char *name_utf8) -{ - char *path_fs = map_directory_child_fs(directory, name_utf8); - if (path_fs == NULL) - return false; - - struct stat st; - bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode); - g_free(path_fs); - - return is_regular; -} - -bool -directory_child_access(const struct directory *directory, - const char *name, int mode) -{ -#ifdef WIN32 - /* access() is useless on WIN32 */ - (void)directory; - (void)name; - (void)mode; - return true; -#else - char *path = map_directory_child_fs(directory, name); - if (path == NULL) - /* something went wrong, but that isn't a permission - problem */ - return true; - - bool success = access(path, mode) == 0 || errno != EACCES; - g_free(path); - return success; -#endif -} diff --git a/src/update_io.h b/src/update_io.h deleted file mode 100644 index 6ff1ccebd..000000000 --- a/src/update_io.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_IO_H -#define MPD_UPDATE_IO_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; - -int -stat_directory(const struct directory *directory, struct stat *st); - -int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st); - -bool -directory_exists(const struct directory *directory); - -bool -directory_child_is_regular(const struct directory *directory, - const char *name_utf8); - -/** - * Checks if the given permissions on the mapped file are given. - */ -bool -directory_child_access(const struct directory *directory, - const char *name, int mode); - -#endif diff --git a/src/update_queue.c b/src/update_queue.c deleted file mode 100644 index 2150fa4e4..000000000 --- a/src/update_queue.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "update_queue.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_queue.h b/src/update_queue.h deleted file mode 100644 index 84ba474b0..000000000 --- a/src/update_queue.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_QUEUE_H -#define MPD_UPDATE_QUEUE_H - -#include "check.h" - -#include <stdbool.h> - -unsigned -update_queue_push(const char *path, bool discard, unsigned base); - -char * -update_queue_shift(bool *discard_r); - -#endif diff --git a/src/update_remove.c b/src/update_remove.c deleted file mode 100644 index f443f5eb2..000000000 --- a/src/update_remove.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_remove.h" -#include "event_pipe.h" -#include "song.h" -#include "playlist.h" -#include "main.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 GMutex *remove_mutex; -static GCond *remove_cond; - -/** - * 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_message("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, global_player_control, removed_song); - - /* clear "removed_song" and send signal to update thread */ - g_mutex_lock(remove_mutex); - removed_song = NULL; - g_cond_signal(remove_cond); - g_mutex_unlock(remove_mutex); -} - -void -update_remove_global_init(void) -{ - remove_mutex = g_mutex_new(); - remove_cond = g_cond_new(); - - event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); -} - -void -update_remove_global_finish(void) -{ - g_mutex_free(remove_mutex); - g_cond_free(remove_cond); -} - -void -update_remove_song(const struct song *song) -{ - assert(removed_song == NULL); - - removed_song = song; - - event_pipe_emit(PIPE_EVENT_DELETE); - - g_mutex_lock(remove_mutex); - - while (removed_song != NULL) - g_cond_wait(remove_cond, remove_mutex); - - g_mutex_unlock(remove_mutex); -} diff --git a/src/update_remove.h b/src/update_remove.h deleted file mode 100644 index 479ef83ff..000000000 --- a/src/update_remove.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_REMOVE_H -#define MPD_UPDATE_REMOVE_H - -#include "check.h" - -struct song; - -void -update_remove_global_init(void); - -void -update_remove_global_finish(void); - -/** - * Sends a signal to the main thread which will in turn remove the - * song: from the sticker database and from the playlist. This - * serialized access is implemented to avoid excessive locking. - */ -void -update_remove_song(const struct song *song); - -#endif diff --git a/src/update_song.c b/src/update_song.c deleted file mode 100644 index f5930d688..000000000 --- a/src/update_song.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_song.h" -#include "update_internal.h" -#include "update_io.h" -#include "update_db.h" -#include "update_container.h" -#include "db_lock.h" -#include "directory.h" -#include "song.h" -#include "decoder_list.h" -#include "decoder_plugin.h" - -#include <glib.h> - -#include <unistd.h> - -static void -update_song_file2(struct directory *directory, - const char *name, const struct stat *st, - const struct decoder_plugin *plugin) -{ - db_lock(); - struct song *song = directory_get_song(directory, name); - db_unlock(); - - if (!directory_child_access(directory, name, R_OK)) { - g_warning("no read permissions on %s/%s", - directory_get_path(directory), name); - if (song != NULL) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (!(song != NULL && st->st_mtime == song->mtime && - !walk_discard) && - update_container_file(directory, name, st, plugin)) { - if (song != NULL) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (song == NULL) { - g_debug("reading %s/%s", - directory_get_path(directory), name); - song = song_file_load(name, directory); - if (song == NULL) { - g_debug("ignoring unrecognized file %s/%s", - directory_get_path(directory), name); - return; - } - - db_lock(); - directory_add_song(directory, song); - db_unlock(); - - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } else if (st->st_mtime != song->mtime || walk_discard) { - g_message("updating %s/%s", - directory_get_path(directory), name); - if (!song_file_update(song)) { - g_debug("deleting unrecognized file %s/%s", - directory_get_path(directory), name); - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - modified = true; - } -} - -bool -update_song_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st) -{ - const struct decoder_plugin *plugin = - decoder_plugin_from_suffix(suffix, NULL); - if (plugin == NULL) - return false; - - update_song_file2(directory, name, st, plugin); - return true; -} diff --git a/src/update_song.h b/src/update_song.h deleted file mode 100644 index cff63f576..000000000 --- a/src/update_song.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_SONG_H -#define MPD_UPDATE_SONG_H - -#include "check.h" - -#include <stdbool.h> -#include <sys/stat.h> - -struct directory; - -bool -update_song_file(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st); - -#endif diff --git a/src/update_walk.c b/src/update_walk.c deleted file mode 100644 index 8554e8f3c..000000000 --- a/src/update_walk.c +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "update_walk.h" -#include "update_io.h" -#include "update_db.h" -#include "update_song.h" -#include "update_archive.h" -#include "database.h" -#include "db_lock.h" -#include "exclude.h" -#include "directory.h" -#include "song.h" -#include "playlist_vector.h" -#include "uri.h" -#include "mapper.h" -#include "path.h" -#include "playlist_list.h" -#include "conf.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> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "update" - -bool walk_discard; -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->have_stat = true; -} - -static void -remove_excluded_from_directory(struct directory *directory, - GSList *exclude_list) -{ - db_lock(); - - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - char *name_fs = utf8_to_fs_charset(directory_get_name(child)); - - if (exclude_list_check(exclude_list, name_fs)) { - delete_directory(child); - modified = true; - } - - g_free(name_fs); - } - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - assert(song->parent == directory); - - char *name_fs = utf8_to_fs_charset(song->uri); - if (exclude_list_check(exclude_list, name_fs)) { - delete_song(directory, song); - modified = true; - } - - g_free(name_fs); - } - - db_unlock(); -} - -static void -purge_deleted_from_directory(struct directory *directory) -{ - struct directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - if (directory_exists(child)) - continue; - - db_lock(); - delete_directory(child); - db_unlock(); - - modified = true; - } - - struct song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - char *path; - struct stat st; - if ((path = map_song_fs(song)) == NULL || - stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { - db_lock(); - delete_song(directory, song); - db_unlock(); - - modified = true; - } - - g_free(path); - } - - struct playlist_metadata *pm, *np; - directory_for_each_playlist_safe(pm, np, directory) { - if (!directory_child_is_regular(directory, pm->name)) { - db_lock(); - playlist_vector_remove(&directory->playlists, pm->name); - db_unlock(); - } - } -} - -#ifndef G_OS_WIN32 -static int -update_directory_stat(struct directory *directory) -{ - struct stat st; - if (stat_directory(directory, &st) < 0) - return -1; - - directory_set_stat(directory, &st); - return 0; -} -#endif - -static int -find_inode_ancestor(struct directory *parent, ino_t inode, dev_t device) -{ -#ifndef G_OS_WIN32 - while (parent) { - if (!parent->have_stat && update_directory_stat(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 bool -update_playlist_file2(struct directory *directory, - const char *name, const char *suffix, - const struct stat *st) -{ - if (!playlist_suffix_supported(suffix)) - return false; - - db_lock(); - if (playlist_vector_update_or_add(&directory->playlists, name, - st->st_mtime)) - modified = true; - db_unlock(); - return true; -} - -static bool -update_regular_file(struct directory *directory, - const char *name, const struct stat *st) -{ - const char *suffix = uri_get_suffix(name); - if (suffix == NULL) - return false; - - return update_song_file(directory, name, suffix, st) || - update_archive_file(directory, name, suffix, st) || - update_playlist_file2(directory, name, suffix, st); -} - -static bool -update_directory(struct directory *directory, const struct stat *st); - -static void -update_directory_child(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)) { - if (find_inode_ancestor(directory, st->st_ino, st->st_dev)) - return; - - db_lock(); - struct directory *subdir = - directory_make_child(directory, name); - db_unlock(); - - assert(directory == subdir->parent); - - if (!update_directory(subdir, st)) { - db_lock(); - delete_directory(subdir); - db_unlock(); - } - } 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 */ -G_GNUC_PURE -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != NULL; -} - -G_GNUC_PURE -static bool -skip_symlink(const struct directory *directory, const char *utf8_name) -{ -#ifndef WIN32 - char *path_fs = map_directory_child_fs(directory, utf8_name); - if (path_fs == NULL) - return true; - - char buffer[MPD_PATH_MAX]; - ssize_t length = readlink(path_fs, buffer, sizeof(buffer)); - g_free(path_fs); - if (length < 0) - /* don't skip if this is not a symlink */ - return errno != EINVAL; - - if ((size_t)length >= sizeof(buffer)) - /* skip symlinks when the buffer is too small for the - link target */ - return true; - - /* null-terminate the buffer, because readlink() will not */ - buffer[length] = 0; - - 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 (g_path_is_absolute(buffer)) { - /* if the symlink points to an absolute path, see if - that path is inside the music directory */ - const char *relative = map_to_relative_path(buffer); - return relative > buffer - ? !follow_inside_symlinks - : !follow_outside_symlinks; - } - - const char *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 -update_directory(struct directory *directory, const struct stat *st) -{ - assert(S_ISDIR(st->st_mode)); - - directory_set_stat(directory, st); - - char *path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return false; - - DIR *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; - } - - char *exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL); - GSList *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); - - purge_deleted_from_directory(directory); - - struct dirent *ent; - 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)) { - modified |= delete_name_in(directory, utf8); - g_free(utf8); - continue; - } - - if (stat_directory_child(directory, utf8, &st2) == 0) - update_directory_child(directory, utf8, &st2); - else - modified |= 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 *name_utf8) -{ - db_lock(); - struct directory *directory = directory_get_child(parent, name_utf8); - db_unlock(); - - if (directory != NULL) - return directory; - - struct stat st; - if (stat_directory_child(parent, name_utf8, &st) < 0 || - find_inode_ancestor(parent, st.st_ino, st.st_dev)) - return NULL; - - if (skip_symlink(parent, name_utf8)) - return NULL; - - /* if we're adding directory paths, make sure to delete filenames - with potentially the same name */ - db_lock(); - struct song *conflicting = directory_get_song(parent, name_utf8); - if (conflicting) - delete_song(parent, conflicting); - - directory = directory_new_child(parent, name_utf8); - db_unlock(); - - directory_set_stat(directory, &st); - return directory; -} - -static struct directory * -directory_make_uri_parent_checked(const char *uri) -{ - struct directory *directory = db_get_root(); - char *duplicated = g_strdup(uri); - char *name_utf8 = duplicated, *slash; - - while ((slash = strchr(name_utf8, '/')) != NULL) { - *slash = 0; - - if (*name_utf8 == 0) - continue; - - directory = directory_make_child_checked(directory, name_utf8); - if (directory == NULL) - break; - - name_utf8 = slash + 1; - } - - g_free(duplicated); - return directory; -} - -static void -update_uri(const char *uri) -{ - struct directory *parent = directory_make_uri_parent_checked(uri); - if (parent == NULL) - return; - - char *name = g_path_get_basename(uri); - - struct stat st; - if (!skip_symlink(parent, name) && - stat_directory_child(parent, name, &st) == 0) - update_directory_child(parent, name, &st); - else - modified |= 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)) { - update_uri(path); - } else { - struct directory *directory = db_get_root(); - struct stat st; - - if (stat_directory(directory, &st) == 0) - update_directory(directory, &st); - } - - return modified; -} diff --git a/src/update_walk.h b/src/update_walk.h deleted file mode 100644 index ab1e41fb9..000000000 --- a/src/update_walk.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_WALK_H -#define MPD_UPDATE_WALK_H - -#include "check.h" - -#include <stdbool.h> - -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); - -#endif diff --git a/src/uri.c b/src/uri.c deleted file mode 100644 index 2a0ca6ca6..000000000 --- a/src/uri.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "uri.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -bool uri_has_scheme(const char *uri) -{ - return strstr(uri, "://") != NULL; -} - -/* suffixes should be ascii only characters */ -const char * -uri_get_suffix(const char *uri) -{ - const char *suffix = strrchr(uri, '.'); - if (suffix == NULL) - return NULL; - - ++suffix; - - if (strpbrk(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 == '/'); - - ++uri; - } -} - -char * -uri_remove_auth(const char *uri) -{ - const char *auth, *slash, *at; - char *p; - - if (strncmp(uri, "http://", 7) == 0) - auth = uri + 7; - else if (strncmp(uri, "https://", 8) == 0) - auth = uri + 8; - else - /* unrecognized URI */ - return NULL; - - slash = strchr(auth, '/'); - if (slash == NULL) - slash = auth + strlen(auth); - - at = memchr(auth, '@', slash - auth); - if (at == NULL) - /* no auth info present, do nothing */ - return NULL; - - /* duplicate the full URI and then delete the auth - information */ - p = g_strdup(uri); - memmove(p + (auth - uri), p + (at + 1 - uri), - strlen(at)); - - return p; -} diff --git a/src/uri.h b/src/uri.h deleted file mode 100644 index 5a9b472f5..000000000 --- a/src/uri.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_URI_H -#define MPD_URI_H - -#include <glib.h> - -#include <stdbool.h> - -/** - * Checks whether the specified URI has a scheme 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); - -#endif diff --git a/src/util/Domain.hxx b/src/util/Domain.hxx new file mode 100644 index 000000000..bbdbf8371 --- /dev/null +++ b/src/util/Domain.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DOMAIN_HXX +#define MPD_DOMAIN_HXX + +class Domain { + const char *const name; + +public: + constexpr explicit Domain(const char *_name) + :name(_name) {} + + Domain(const Domain &) = delete; + Domain &operator=(const Domain &) = delete; + + constexpr const char *GetName() const { + return name; + } + + bool operator==(const Domain &other) const { + return this == &other; + } + + bool operator!=(const Domain &other) const { + return !(*this == other); + } +}; + +#endif diff --git a/src/util/Error.cxx b/src/util/Error.cxx new file mode 100644 index 000000000..1e6bd9cff --- /dev/null +++ b/src/util/Error.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Error.hxx" +#include "Domain.hxx" + +#include <glib.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> + +const Domain errno_domain("errno"); + +Error::~Error() {} + +void +Error::Set(const Domain &_domain, int _code, const char *_message) +{ + domain = &_domain; + code = _code; + message.assign(_message); +} + +void +Error::Format2(const Domain &_domain, int _code, const char *fmt, ...) +{ + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + Set(_domain, _code, buffer); +} + +void +Error::FormatPrefix(const char *fmt, ...) +{ + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + AddPrefix(buffer); +} + +void +Error::SetErrno(int e) +{ + Set(errno_domain, e, g_strerror(e)); +} + +void +Error::SetErrno() +{ + SetErrno(errno); +} + +void +Error::SetErrno(int e, const char *prefix) +{ + Format(errno_domain, e, "%s: %s", prefix, g_strerror(e)); +} + +void +Error::SetErrno(const char *prefix) +{ + SetErrno(errno, prefix); +} + +void +Error::FormatErrno(int e, const char *fmt, ...) +{ + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + SetErrno(e, buffer); +} + +void +Error::FormatErrno(const char *fmt, ...) +{ + const int e = errno; + + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + SetErrno(e, buffer); +} diff --git a/src/util/Error.hxx b/src/util/Error.hxx new file mode 100644 index 000000000..1fcf46061 --- /dev/null +++ b/src/util/Error.hxx @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_ERROR_HXX + +#include "check.h" +#include "gcc.h" + +#include <string> + +#include <assert.h> + +class Domain; + +extern const Domain errno_domain; + +#ifdef WIN32 +/* fuck WIN32! */ +#include <windows.h> +#define IgnoreError MPDIgnoreError +#undef GetMessage +#endif + +/** + * This class contains information about a runtime error. + */ +class Error { + const Domain *domain; + int code; + std::string message; + +public: + Error():domain(nullptr), code(0) {} + + Error(const Domain &_domain, int _code, const char *_message) + :domain(&_domain), code(_code), message(_message) {} + + Error(const Domain &_domain, const char *_message) + :domain(&_domain), code(0), message(_message) {} + + Error(Error &&other) + :domain(other.domain), code(other.code), + message(std::move(other.message)) {} + + ~Error(); + + Error(const Error &) = delete; + Error &operator=(const Error &) = delete; + + Error &operator=(Error &&other) { + domain = other.domain; + code = other.code; + std::swap(message, other.message); + return *this; + } + + bool IsDefined() const { + return domain != nullptr; + } + + void Clear() { + domain = nullptr; + } + + const Domain &GetDomain() const { + assert(IsDefined()); + + return *domain; + } + + bool IsDomain(const Domain &other) const { + return domain == &other; + } + + int GetCode() const { + assert(IsDefined()); + + return code; + } + + const char *GetMessage() const { + assert(IsDefined()); + + return message.c_str(); + } + + void Set(const Error &other) { + assert(!IsDefined()); + assert(other.IsDefined()); + + domain = other.domain; + code = other.code; + message = other.message; + } + + void Set(const Domain &_domain, int _code, const char *_message); + + void Set(const Domain &_domain, const char *_message) { + Set(_domain, 0, _message); + } + +private: + void Format2(const Domain &_domain, int _code, const char *fmt, ...); + +public: + template<typename... Args> + void Format(const Domain &_domain, int _code, + const char *fmt, Args&&... args) { + Format2(_domain, _code, fmt, std::forward<Args>(args)...); + } + + template<typename... Args> + void Format(const Domain &_domain, const char *fmt, Args&&... args) { + Format2(_domain, 0, fmt, std::forward<Args>(args)...); + } + + void AddPrefix(const char *prefix) { + message.insert(0, prefix); + } + + void FormatPrefix(const char *fmt, ...); + + void SetErrno(int e); + void SetErrno(); + void SetErrno(int e, const char *prefix); + void SetErrno(const char *prefix); + void FormatErrno(const char *prefix, ...); + void FormatErrno(int e, const char *prefix, ...); +}; + +/** + * Pass a temporary instance of this class to ignore errors. + */ +class IgnoreError final { + Error error; + +public: + operator Error &() { + assert(!error.IsDefined()); + + return error; + } +}; + +#endif diff --git a/src/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx new file mode 100644 index 000000000..d1c55c965 --- /dev/null +++ b/src/util/HugeAllocator.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "HugeAllocator.hxx" + +#ifdef __linux__ +#include <sys/mman.h> +#include <unistd.h> +#else +#include <stdlib.h> +#endif + +#ifdef __linux__ + +/** + * Round up the parameter, make it page-aligned. + */ +gcc_const +static size_t +AlignToPageSize(size_t size) +{ + static const long page_size = sysconf(_SC_PAGESIZE); + if (page_size > 0) + return size; + + size_t ps(page_size); + return (size + ps - 1) / ps * ps; +} + +void * +HugeAllocate(size_t size) +{ + size = AlignToPageSize(size); + + constexpr int flags = MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE; + void *p = mmap(nullptr, size, + PROT_READ|PROT_WRITE, flags, + -1, 0); + if (p == (void *)-1) + return nullptr; + +#ifdef MADV_HUGEPAGE + /* allow the Linux kernel to use "Huge Pages", which reduces page + table overhead for this big chunk of data */ + madvise(p, size, MADV_HUGEPAGE); +#endif + +#ifdef MADV_DONTFORK + /* just in case MPD needs to fork, don't copy this allocation + to the child process, to reduce overhead */ + madvise(p, size, MADV_DONTFORK); +#endif + + return p; +} + +void +HugeFree(void *p, size_t size) +{ + munmap(p, AlignToPageSize(size)); +} + +void +HugeDiscard(void *p, size_t size) +{ +#ifdef MADV_DONTNEED + madvise(p, AlignToPageSize(size), MADV_DONTNEED); +#endif +} + +#endif diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx new file mode 100644 index 000000000..01c92cd43 --- /dev/null +++ b/src/util/HugeAllocator.hxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HUGE_ALLOCATOR_HXX +#define MPD_HUGE_ALLOCATOR_HXX + +#include "gcc.h" + +#include <stddef.h> + +#ifdef __linux__ + +/** + * Allocate a huge amount of memory. This will be done in a way that + * allows giving the memory back to the kernel as soon as we don't + * need it anymore. On the downside, this call is expensive. + */ +gcc_malloc +void * +HugeAllocate(size_t size); + +/** + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeFree(void *p, size_t size); + +/** + * Discard any data stored in the allocation and give the memory back + * to the kernel. After returning, the allocation still exists and + * can be reused at any time, but its contents are undefined. + * + * @param p an allocation returned by HugeAllocate() + * @param size the allocation's size as passed to HugeAllocate() + */ +void +HugeDiscard(void *p, size_t size); + +#else + +/* not Linux: fall back to standard C calls */ + +#include <stdlib.h> + +gcc_malloc +static inline void * +HugeAllocate(size_t size) +{ + return malloc(size); +} + +static inline void +HugeFree(void *p, size_t) +{ + free(p); +} + +static inline void +HugeDiscard(void *, size_t) +{ +} + +#endif + +#endif diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx new file mode 100644 index 000000000..0f90ebb2e --- /dev/null +++ b/src/util/LazyRandomEngine.cxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "LazyRandomEngine.hxx" + +void +LazyRandomEngine::AutoCreate() +{ + if (engine != nullptr) + return; + + std::random_device rd; + engine = new std::mt19937(rd()); +} diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx new file mode 100644 index 000000000..8afe1d1c0 --- /dev/null +++ b/src/util/LazyRandomEngine.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LAZY_RANDOM_ENGINE_HXX +#define MPD_LAZY_RANDOM_ENGINE_HXX + +#include "check.h" + +#include <random> + +#include <assert.h> + +/** + * A random engine that will be created and seeded on demand. + */ +class LazyRandomEngine { + std::mt19937 *engine; + +public: + typedef std::mt19937::result_type result_type; + + LazyRandomEngine():engine(nullptr) {} + ~LazyRandomEngine() { + delete engine; + } + + LazyRandomEngine(const LazyRandomEngine &other) = delete; + LazyRandomEngine &operator=(const LazyRandomEngine &other) = delete; + + /** + * Create and seed the real engine. Call this before any + * other method. + */ + void AutoCreate(); + + result_type min() const { + return engine->min(); + } + + result_type max() const { + return engine->max(); + } + + result_type operator()() { + assert(engine != nullptr); + + return engine->operator()(); + } +}; + +#endif diff --git a/src/util/Manual.hxx b/src/util/Manual.hxx new file mode 100644 index 000000000..802695224 --- /dev/null +++ b/src/util/Manual.hxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + */ + +#ifndef MPD_MANUAL_HXX +#define MPD_MANUAL_HXX + +#include "gcc.h" + +#include <new> +#include <utility> + +#if !defined(__clang__) && __GNUC__ && !GCC_CHECK_VERSION(4,8) +#include <type_traits> +#endif + +#include <assert.h> + +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +/** + * Container for an object that gets constructed and destructed + * manually. The object is constructed in-place, and therefore + * without allocation overhead. It can be constructed and destructed + * repeatedly. + */ +template<class T> +class Manual { +#if !defined(__clang__) && __GNUC__ && !GCC_CHECK_VERSION(4,8) + /* no alignas() on gcc < 4.8: apply worst-case fallback */ + __attribute__((aligned(8))) +#else + alignas(T) +#endif + char data[sizeof(T)]; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + Manual():initialized(false) {} + ~Manual() { + assert(!initialized); + } +#endif + + template<typename... Args> + void Construct(Args&&... args) { + assert(!initialized); + + void *p = data; + new(p) T(std::forward<Args>(args)...); + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Destruct() { + assert(initialized); + + T *t = (T *)data; + t->T::~T(); + +#ifndef NDEBUG + initialized = false; +#endif + } + + operator T &() { + return *(T *)data; + } + + operator const T &() const { + return *(const T *)data; + } + + T *operator->() { + return (T *)data; + } + + const T *operator->() const { + return (T *)data; + } +}; + +#if defined(__clang__) || GCC_VERSION >= 40700 +#pragma GCC diagnostic pop +#endif + +#endif diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx new file mode 100644 index 000000000..a3659b8f4 --- /dev/null +++ b/src/util/PeakBuffer.cxx @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PeakBuffer.hxx" +#include "HugeAllocator.hxx" +#include "fifo_buffer.h" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +PeakBuffer::~PeakBuffer() +{ + if (normal_buffer != nullptr) + fifo_buffer_free(normal_buffer); + + if (peak_buffer != nullptr) + HugeFree(peak_buffer, peak_size); +} + +bool +PeakBuffer::IsEmpty() const +{ + return (normal_buffer == nullptr || + fifo_buffer_is_empty(normal_buffer)) && + (peak_buffer == nullptr || + fifo_buffer_is_empty(peak_buffer)); +} + +const void * +PeakBuffer::Read(size_t *length_r) const +{ + if (normal_buffer != nullptr) { + const void *p = fifo_buffer_read(normal_buffer, length_r); + if (p != nullptr) + return p; + } + + if (peak_buffer != nullptr) { + const void *p = fifo_buffer_read(peak_buffer, length_r); + if (p != nullptr) + return p; + } + + return nullptr; +} + +void +PeakBuffer::Consume(size_t length) +{ + if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) { + fifo_buffer_consume(normal_buffer, length); + return; + } + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + fifo_buffer_consume(peak_buffer, length); + if (fifo_buffer_is_empty(peak_buffer)) { + HugeFree(peak_buffer, peak_size); + peak_buffer = nullptr; + } + + return; + } +} + +static size_t +AppendTo(fifo_buffer *buffer, const void *data, size_t length) +{ + assert(data != nullptr); + assert(length > 0); + + size_t total = 0; + + do { + size_t max_length; + void *p = fifo_buffer_write(buffer, &max_length); + if (p == nullptr) + break; + + const size_t nbytes = std::min(length, max_length); + memcpy(p, data, nbytes); + fifo_buffer_append(buffer, nbytes); + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + total += nbytes; + } while (length > 0); + + return total; +} + +bool +PeakBuffer::Append(const void *data, size_t length) +{ + if (length == 0) + return true; + + if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { + size_t nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; + } + + if (normal_buffer == nullptr) + normal_buffer = fifo_buffer_new(normal_size); + + size_t nbytes = AppendTo(normal_buffer, data, length); + if (nbytes > 0) { + data = (const uint8_t *)data + nbytes; + length -= nbytes; + if (length == 0) + return true; + } + + if (peak_buffer == nullptr && peak_size > 0) { + peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); + if (peak_buffer == nullptr) + return false; + + fifo_buffer_init(peak_buffer, peak_size); + } + + nbytes = AppendTo(peak_buffer, data, length); + return nbytes == length; +} diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx new file mode 100644 index 000000000..0fbba8d77 --- /dev/null +++ b/src/util/PeakBuffer.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PEAK_BUFFER_HXX +#define MPD_PEAK_BUFFER_HXX + +#include "gcc.h" + +#include <stddef.h> + +struct fifo_buffer; + +/** + * A FIFO-like buffer that will allocate more memory on demand to + * allow large peaks. This second buffer will be given back to the + * kernel when it has been consumed. + */ +class PeakBuffer { + size_t normal_size, peak_size; + + fifo_buffer *normal_buffer, *peak_buffer; + +public: + PeakBuffer(size_t _normal_size, size_t _peak_size) + :normal_size(_normal_size), peak_size(_peak_size), + normal_buffer(nullptr), peak_buffer(nullptr) {} + + PeakBuffer(PeakBuffer &&other) + :normal_size(other.normal_size), peak_size(other.peak_size), + normal_buffer(other.normal_buffer), + peak_buffer(other.peak_buffer) { + other.normal_buffer = nullptr; + other.peak_buffer = nullptr; + } + + ~PeakBuffer(); + + PeakBuffer(const PeakBuffer &) = delete; + PeakBuffer &operator=(const PeakBuffer &) = delete; + + gcc_pure + bool IsEmpty() const; + + const void *Read(size_t *length_r) const; + void Consume(size_t length); + + bool Append(const void *data, size_t length); +}; + +#endif diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx new file mode 100644 index 000000000..9a45a585b --- /dev/null +++ b/src/util/RefCount.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 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_HXX +#define MPD_REFCOUNT_HXX + +#include <atomic> + +class RefCount { + std::atomic_uint n; + +public: + constexpr RefCount():n(1) {} + + void Increment() { + ++n; + } + + /** + * @return true if the number of references has been dropped to 0 + */ + bool Decrement() { + return --n == 0; + } +}; + +#endif diff --git a/src/util/ReusableArray.hxx b/src/util/ReusableArray.hxx new file mode 100644 index 000000000..30b1a9cd9 --- /dev/null +++ b/src/util/ReusableArray.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + */ + +#ifndef REUSABLE_ARRAY_HXX +#define REUSABLE_ARRAY_HXX + +#include <stddef.h> + +#include "gcc.h" + +/** + * Manager for a temporary array which grows as needed. This attempts + * to reduce the number of consecutive heap allocations and + * deallocations. + * + * @param T the array element type + * @param M always allocate multiples of this number; must be a power of 2 + */ +template<typename T, size_t M=1> +class ReusableArray { + T *buffer; + size_t capacity; + +public: + ReusableArray():buffer(nullptr), capacity(0) {} + + ReusableArray(const ReusableArray &other) = delete; + ReusableArray &operator=(const ReusableArray &other) = delete; + + ~ReusableArray() { + delete[] buffer; + } + + /** + * Free resources allocated by this object. This invalidates + * the buffer returned by Get(). + */ + void Clear() { + delete[] buffer; + buffer = nullptr; + capacity = 0; + } + + /** + * Get the buffer, and guarantee a minimum size. This buffer + * becomes invalid with the next Get() call. + */ + gcc_malloc + T *Get(size_t size) { + if (gcc_unlikely(size > capacity)) { + /* too small: grow */ + delete[] buffer; + + capacity = ((size - 1) | (M - 1)) + 1; + buffer = new T[capacity]; + } + + return buffer; + } +}; + +#endif diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx new file mode 100644 index 000000000..c61f164f4 --- /dev/null +++ b/src/util/SliceBuffer.hxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SLICE_BUFFER_HXX +#define MPD_SLICE_BUFFER_HXX + +#include "HugeAllocator.hxx" +#include "gcc.h" + +#include <utility> +#include <new> + +#include <assert.h> +#include <stddef.h> + +/** + * This class pre-allocates a certain number of objects, and allows + * callers to allocate and free these objects ("slices"). + */ +template<typename T> +class SliceBuffer { + union Slice { + Slice *next; + + T value; + }; + + /** + * The maximum number of slices in this container. + */ + const unsigned n_max; + + /** + * The number of slices that are initialized. This is used to + * avoid page faulting on the new allocation, so the kernel + * does not need to reserve physical memory pages. + */ + unsigned n_initialized; + + /** + * The number of slices currently allocated. + */ + unsigned n_allocated; + + Slice *const data; + + /** + * Pointer to the first free element in the chain. + */ + Slice *available; + + size_t CalcAllocationSize() const { + return n_max * sizeof(Slice); + } + +public: + SliceBuffer(unsigned _count) + :n_max(_count), n_initialized(0), n_allocated(0), + data((Slice *)HugeAllocate(CalcAllocationSize())), + available(nullptr) { + assert(n_max > 0); + } + + ~SliceBuffer() { + /* all slices must be freed explicitly, and this + assertion checks for leaks */ + assert(n_allocated == 0); + + HugeFree(data, CalcAllocationSize()); + } + + SliceBuffer(const SliceBuffer &other) = delete; + SliceBuffer &operator=(const SliceBuffer &other) = delete; + + /** + * @return true if buffer allocation (by the constructor) has failed + */ + bool IsOOM() { + return data == nullptr; + } + + unsigned GetCapacity() const { + return n_max; + } + + bool IsEmpty() const { + return n_allocated == 0; + } + + bool IsFull() const { + return n_allocated == n_max; + } + + template<typename... Args> + T *Allocate(Args&&... args) { + assert(n_initialized <= n_max); + assert(n_allocated <= n_initialized); + + if (available == nullptr) { + if (n_initialized == n_max) { + /* out of (internal) memory, buffer is full */ + assert(n_allocated == n_max); + return nullptr; + } + + available = &data[n_initialized++]; + available->next = nullptr; + } + + /* allocate a slice */ + T *value = &available->value; + available = available->next; + ++n_allocated; + + /* construct the object */ + return ::new((void *)value) T(std::forward<Args>(args)...); + } + + void Free(T *value) { + assert(n_initialized <= n_max); + assert(n_allocated > 0); + assert(n_allocated <= n_initialized); + + Slice *slice = reinterpret_cast<Slice *>(value); + assert(slice >= data && slice < data + n_max); + + /* destruct the object */ + value->~T(); + + /* insert the slice in the "available" linked list */ + slice->next = available; + available = slice; + --n_allocated; + + /* give memory back to the kernel when the last slice + was freed */ + if (n_allocated == 0) { + HugeDiscard(data, CalcAllocationSize()); + n_initialized = 0; + available = nullptr; + } + } +}; + +#endif diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx new file mode 100644 index 000000000..87d032735 --- /dev/null +++ b/src/util/StringUtil.cxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "StringUtil.hxx" + +#include <glib.h> + +#include <assert.h> + +const char * +strchug_fast_c(const char *p) +{ + while (*p != 0 && g_ascii_isspace(*p)) + ++p; + + return p; +} + +bool +string_array_contains(const char *const* haystack, const char *needle) +{ + assert(haystack != nullptr); + assert(needle != nullptr); + + for (; *haystack != nullptr; ++haystack) + if (g_ascii_strcasecmp(*haystack, needle) == 0) + return true; + + return false; +} diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx new file mode 100644 index 000000000..6eeca893d --- /dev/null +++ b/src/util/StringUtil.hxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STRING_UTIL_HXX +#define MPD_STRING_UTIL_HXX + +#include "gcc.h" + +/** + * Returns a pointer to the first non-whitespace character in the + * string, or to the end of the string. + * + * This is a faster version of g_strchug(), because it does not move + * data. + */ +gcc_pure +const char * +strchug_fast_c(const char *p); + +/** + * Same as strchug_fast_c(), but works with a writable pointer. + */ +gcc_pure +static inline char * +strchug_fast(char *p) +{ + return const_cast<char *>(strchug_fast_c(p)); +} + +/** + * Checks whether a string array contains the specified string. + * + * @param haystack a NULL terminated list of strings + * @param needle the string to search for; the comparison is + * case-insensitive for ASCII characters + * @return true if found + */ +gcc_pure +bool +string_array_contains(const char *const* haystack, const char *needle); + +#endif diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx new file mode 100644 index 000000000..726da0dd6 --- /dev/null +++ b/src/util/Tokenizer.cxx @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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.hxx" +#include "StringUtil.hxx" +#include "Error.hxx" +#include "Domain.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain tokenizer_domain("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::NextWord(Error &error) +{ + char *const word = input; + + if (*input == 0) + return nullptr; + + /* check the first character */ + + if (!valid_word_first_char(*input)) { + error.Set(tokenizer_domain, "Letter expected"); + return nullptr; + } + + /* 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 = strchug_fast(input + 1); + break; + } + + if (!valid_word_char(*input)) { + error.Set(tokenizer_domain, "Invalid word character"); + return nullptr; + } + } + + /* end of string: the string is already null-terminated + here */ + + return word; +} + +static inline bool +valid_unquoted_char(char ch) +{ + return (unsigned char)ch > 0x20 && ch != '"' && ch != '\''; +} + +char * +Tokenizer::NextUnquoted(Error &error) +{ + char *const word = input; + + if (*input == 0) + return nullptr; + + /* check the first character */ + + if (!valid_unquoted_char(*input)) { + error.Set(tokenizer_domain, "Invalid unquoted character"); + return nullptr; + } + + /* 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 = strchug_fast(input + 1); + break; + } + + if (!valid_unquoted_char(*input)) { + error.Set(tokenizer_domain, + "Invalid unquoted character"); + return nullptr; + } + } + + /* end of string: the string is already null-terminated + here */ + + return word; +} + +char * +Tokenizer::NextString(Error &error) +{ + char *const word = input, *dest = input; + + if (*input == 0) + /* end of line */ + return nullptr; + + /* check for the opening " */ + + if (*input != '"') { + error.Set(tokenizer_domain, "'\"' expected"); + return nullptr; + } + + ++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; + error.Set(tokenizer_domain, "Missing closing '\"'"); + return nullptr; + } + + /* copy one character */ + *dest++ = *input++; + } + + /* the following character must be a whitespace (or end of + line) */ + + ++input; + if (*input != 0 && !g_ascii_isspace(*input)) { + error.Set(tokenizer_domain, + "Space expected after closing '\"'"); + return nullptr; + } + + /* finish the string and return it */ + + *dest = 0; + input = strchug_fast(input); + return word; +} + +char * +Tokenizer::NextParam(Error &error) +{ + if (*input == '"') + return NextString(error); + else + return NextUnquoted(error); +} diff --git a/src/util/Tokenizer.hxx b/src/util/Tokenizer.hxx new file mode 100644 index 000000000..a689dc31d --- /dev/null +++ b/src/util/Tokenizer.hxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HXX +#define MPD_TOKENIZER_HXX + +class Error; + +class Tokenizer { + char *input; + +public: + /** + * @param _input the input string; the contents will be + * modified by this class + */ + constexpr Tokenizer(char *_input):input(_input) {} + + Tokenizer(const Tokenizer &) = delete; + Tokenizer &operator=(const Tokenizer &) = delete; + + char *Rest() { + return input; + } + + char CurrentChar() const { + return *input; + } + + bool IsEnd() const { + return CurrentChar() == 0; + } + + /** + * Reads the next word. + * + * @param error if this function returns nullptr and + * **input_p!=0, it provides an #Error object in + * this argument + * @return a pointer to the null-terminated word, or nullptr + * on error or end of line + */ + char *NextWord(Error &error); + + /** + * Reads the next unquoted word from the input string. + * + * @param error_r if this function returns nullptr and **input_p!=0, it + * provides an #Error object in this argument + * @return a pointer to the null-terminated word, or nullptr + * on error or end of line + */ + char *NextUnquoted(Error &error); + + /** + * 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 nullptr and **input_p!=0, it + * provides an #Error object in this argument + * @return a pointer to the null-terminated string, or nullptr on error + * or end of line + */ + char *NextString(Error &error); + + /** + * Reads the next unquoted word or quoted string from the + * input. This is a wrapper for NextUnquoted() and + * NextString(). + * + * @param error_r if this function returns nullptr and + * **input_p!=0, it provides an #Error object in + * this argument + * @return a pointer to the null-terminated string, or nullptr + * on error or end of line + */ + char *NextParam(Error &error); +}; + +#endif diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx new file mode 100644 index 000000000..4b0cec11b --- /dev/null +++ b/src/util/UriUtil.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "UriUtil.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +bool uri_has_scheme(const char *uri) +{ + return strstr(uri, "://") != nullptr; +} + +/* suffixes should be ascii only characters */ +const char * +uri_get_suffix(const char *uri) +{ + const char *suffix = strrchr(uri, '.'); + if (suffix == nullptr) + return nullptr; + + ++suffix; + + if (strpbrk(suffix, "/\\") != nullptr) + return nullptr; + + 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 nullptr; + + q = strchr(p + 1, '/'); + return q != nullptr ? q : ""; +} + +bool +uri_safe_local(const char *uri) +{ + while (true) { + uri = verify_uri_segment(uri); + if (uri == nullptr) + return false; + + if (*uri == 0) + return true; + + assert(*uri == '/'); + + ++uri; + } +} + +char * +uri_remove_auth(const char *uri) +{ + const char *auth, *slash, *at; + char *p; + + if (strncmp(uri, "http://", 7) == 0) + auth = uri + 7; + else if (strncmp(uri, "https://", 8) == 0) + auth = uri + 8; + else + /* unrecognized URI */ + return nullptr; + + slash = strchr(auth, '/'); + if (slash == nullptr) + slash = auth + strlen(auth); + + at = (const char *)memchr(auth, '@', slash - auth); + if (at == nullptr) + /* no auth info present, do nothing */ + return nullptr; + + /* duplicate the full URI and then delete the auth + information */ + p = g_strdup(uri); + memmove(p + (auth - uri), p + (at + 1 - uri), + strlen(at)); + + return p; +} diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx new file mode 100644 index 000000000..1d288ca1d --- /dev/null +++ b/src/util/UriUtil.hxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_URI_UTIL_HXX +#define MPD_URI_UTIL_HXX + +#include "gcc.h" + +/** + * Checks whether the specified URI has a scheme in the form + * "scheme://". + */ +gcc_pure +bool uri_has_scheme(const char *uri); + +gcc_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 + */ +gcc_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. + */ +gcc_malloc +char * +uri_remove_auth(const char *uri); + +#endif diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h index e44693b1d..54cb789bb 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -20,12 +20,13 @@ #ifndef MPD_BIT_REVERSE_H #define MPD_BIT_REVERSE_H -#include <glib.h> +#include "gcc.h" + #include <stdint.h> extern const uint8_t bit_reverse_table[256]; -G_GNUC_CONST +gcc_const static inline uint8_t bit_reverse(uint8_t x) { diff --git a/src/util/fifo_buffer.c b/src/util/fifo_buffer.c new file mode 100644 index 000000000..162ddf946 --- /dev/null +++ b/src/util/fifo_buffer.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2003-2011 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" +#include "fifo_buffer.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct fifo_buffer { + size_t size, start, end; + unsigned char buffer[sizeof(size_t)]; +}; + +struct fifo_buffer * +fifo_buffer_new(size_t size) +{ + struct fifo_buffer *buffer; + + assert(size > 0); + + buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) - + sizeof(buffer->buffer) + size); + + buffer->size = size; + buffer->start = 0; + buffer->end = 0; + + return buffer; +} + +void +fifo_buffer_init(struct fifo_buffer *buffer, size_t size) +{ + buffer->size = size - (sizeof(*buffer) - sizeof(buffer->buffer)); + buffer->start = 0; + buffer->end = 0; +} + +static void +fifo_buffer_move(struct fifo_buffer *buffer); + +struct fifo_buffer * +fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size) +{ + if (buffer == NULL) + return new_size > 0 + ? fifo_buffer_new(new_size) + : NULL; + + /* existing data must fit in new size */ + assert(new_size >= buffer->end - buffer->start); + + if (new_size == 0) { + fifo_buffer_free(buffer); + return NULL; + } + + /* compress the buffer when we're shrinking and the tail of + the buffer would exceed the new size */ + if (buffer->end > new_size) + fifo_buffer_move(buffer); + + /* existing data must fit in new size: second check */ + assert(buffer->end <= new_size); + + buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) + + new_size); + buffer->size = new_size; + return buffer; +} + +void +fifo_buffer_free(struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + g_free(buffer); +} + +size_t +fifo_buffer_capacity(const struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + return buffer->size; +} + +size_t +fifo_buffer_available(const struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + return buffer->end - buffer->start; +} + +void +fifo_buffer_clear(struct fifo_buffer *buffer) +{ + assert(buffer != NULL); + + buffer->start = 0; + buffer->end = 0; +} + +const void * +fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r) +{ + assert(buffer != NULL); + assert(buffer->end >= buffer->start); + assert(length_r != NULL); + + if (buffer->start == buffer->end) + /* the buffer is empty */ + return NULL; + + *length_r = buffer->end - buffer->start; + return buffer->buffer + buffer->start; +} + +void +fifo_buffer_consume(struct fifo_buffer *buffer, size_t length) +{ + assert(buffer != NULL); + assert(buffer->end >= buffer->start); + assert(buffer->start + length <= buffer->end); + + buffer->start += length; +} + +/** + * Move data to the beginning of the buffer, to make room at the end. + */ +static void +fifo_buffer_move(struct fifo_buffer *buffer) +{ + if (buffer->start == 0) + return; + + if (buffer->end > buffer->start) + memmove(buffer->buffer, + buffer->buffer + buffer->start, + buffer->end - buffer->start); + + buffer->end -= buffer->start; + buffer->start = 0; +} + +void * +fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r) +{ + assert(buffer != NULL); + assert(buffer->end <= buffer->size); + assert(max_length_r != NULL); + + if (buffer->end == buffer->size) { + fifo_buffer_move(buffer); + if (buffer->end == buffer->size) + return NULL; + } else if (buffer->start > 0 && buffer->start == buffer->end) { + buffer->start = 0; + buffer->end = 0; + } + + *max_length_r = buffer->size - buffer->end; + return buffer->buffer + buffer->end; +} + +void +fifo_buffer_append(struct fifo_buffer *buffer, size_t length) +{ + assert(buffer != NULL); + assert(buffer->end >= buffer->start); + assert(buffer->end + length <= buffer->size); + + buffer->end += length; +} + +bool +fifo_buffer_is_empty(struct fifo_buffer *buffer) +{ + return buffer->start == buffer->end; +} + +bool +fifo_buffer_is_full(struct fifo_buffer *buffer) +{ + return buffer->start == 0 && buffer->end == buffer->size; +} diff --git a/src/util/fifo_buffer.h b/src/util/fifo_buffer.h new file mode 100644 index 000000000..ccea97d86 --- /dev/null +++ b/src/util/fifo_buffer.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2011 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 + * + * This is a general purpose FIFO buffer library. You may append data + * at the end, while another instance reads data from the beginning. + * It is optimized for zero-copy usage: you get pointers to the real + * buffer, where you may operate on. + * + * This library is not thread safe. + */ + +#ifndef MPD_FIFO_BUFFER_H +#define MPD_FIFO_BUFFER_H + +#include <stdbool.h> +#include <stddef.h> + +struct fifo_buffer; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Creates a new #fifo_buffer object. Free this object with + * fifo_buffer_free(). + * + * @param size the size of the buffer in bytes + * @return the new #fifo_buffer object + */ +struct fifo_buffer * +fifo_buffer_new(size_t size); + +void +fifo_buffer_init(struct fifo_buffer *buffer, size_t size); + +/** + * Change the capacity of the #fifo_buffer, while preserving existing + * data. + * + * @param buffer the old buffer, may be NULL + * @param new_size the requested new size of the #fifo_buffer; must + * not be smaller than the data which is stored in the old buffer + * @return the new buffer, may be NULL if the requested new size is 0 + */ +struct fifo_buffer * +fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size); + +/** + * Frees the resources consumed by this #fifo_buffer object. + */ +void +fifo_buffer_free(struct fifo_buffer *buffer); + +/** + * Return the capacity of the buffer, i.e. the size that was passed to + * fifo_buffer_new(). + */ +size_t +fifo_buffer_capacity(const struct fifo_buffer *buffer); + +/** + * Return the number of bytes currently stored in the buffer. + */ +size_t +fifo_buffer_available(const struct fifo_buffer *buffer); + +/** + * Clears all data currently in this #fifo_buffer object. This does + * not overwrite the actuall buffer; it just resets the internal + * pointers. + */ +void +fifo_buffer_clear(struct fifo_buffer *buffer); + +/** + * Reads from the beginning of the buffer. To remove consumed data + * from the buffer, call fifo_buffer_consume(). + * + * @param buffer the #fifo_buffer object + * @param length_r the maximum amount to read is returned here + * @return a pointer to the beginning of the buffer, or NULL if the + * buffer is empty + */ +const void * +fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r); + +/** + * Marks data at the beginning of the buffer as "consumed". + * + * @param buffer the #fifo_buffer object + * @param length the number of bytes which were consumed + */ +void +fifo_buffer_consume(struct fifo_buffer *buffer, size_t length); + +/** + * Prepares writing to the buffer. This returns a buffer which you + * can write to. To commit the write operation, call + * fifo_buffer_append(). + * + * @param buffer the #fifo_buffer object + * @param max_length_r the maximum amount to write is returned here + * @return a pointer to the end of the buffer, or NULL if the buffer + * is already full + */ +void * +fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r); + +/** + * Commits the write operation initiated by fifo_buffer_write(). + * + * @param buffer the #fifo_buffer object + * @param length the number of bytes which were written + */ +void +fifo_buffer_append(struct fifo_buffer *buffer, size_t length); + +/** + * Checks if the buffer is empty. + */ +bool +fifo_buffer_is_empty(struct fifo_buffer *buffer); + +/** + * Checks if the buffer is full. + */ +bool +fifo_buffer_is_full(struct fifo_buffer *buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/growing_fifo.c b/src/util/growing_fifo.c index 88431f60e..88431f60e 100644 --- a/src/growing_fifo.c +++ b/src/util/growing_fifo.c diff --git a/src/growing_fifo.h b/src/util/growing_fifo.h index 723c3b3ff..723c3b3ff 100644 --- a/src/growing_fifo.h +++ b/src/util/growing_fifo.h diff --git a/src/util/list.h b/src/util/list.h index fdab47675..73d99befa 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -25,8 +25,6 @@ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H -#include <glib.h> - #ifdef __clang__ /* allow typeof() */ #pragma GCC diagnostic ignored "-Wlanguage-extension-token" @@ -40,15 +38,15 @@ * */ #define container_of(ptr, type, member) \ - (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member))) + ((type *)((uint8_t *)ptr - offsetof(type, member))) /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ -#define LIST_POISON1 ((void *) 0x00100100) -#define LIST_POISON2 ((void *) 0x00200200) +#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100) +#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200) /* * Simple doubly linked list implementation. @@ -82,46 +80,47 @@ static inline void INIT_LIST_HEAD(struct list_head *list) * the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST -static inline void __list_add(struct list_head *new, +static inline void __list_add(struct list_head *new_item, struct list_head *prev, struct list_head *next) { - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; + next->prev = new_item; + new_item->next = next; + new_item->prev = prev; + prev->next = new_item; } #else -extern void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next); +extern void __list_add(struct list_head *new_item, + struct list_head *prev, + struct list_head *next); #endif /** * list_add - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ -static inline void list_add(struct list_head *new, struct list_head *head) +static inline void list_add(struct list_head *new_item, struct list_head *head) { - __list_add(new, head, head->next); + __list_add(new_item, head, head->next); } /** * list_add_tail - add a new entry - * @new: new entry to be added + * @new_item: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) +static inline void +list_add_tail(struct list_head *new_item, struct list_head *head) { - __list_add(new, head->prev, head); + __list_add(new_item, head->prev, head); } /* @@ -163,23 +162,23 @@ extern void list_del(struct list_head *entry); /** * list_replace - replace old entry by new one * @old : the element to be replaced - * @new : the new element to insert + * @new_item : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - new->next = old->next; - new->next->prev = new; - new->prev = old->prev; - new->prev->next = new; + new_item->next = old->next; + new_item->next->prev = new_item; + new_item->prev = old->prev; + new_item->prev->next = new_item; } static inline void list_replace_init(struct list_head *old, - struct list_head *new) + struct list_head *new_item) { - list_replace(old, new); + list_replace(old, new_item); INIT_LIST_HEAD(old); } diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index a2de3212e..000000000 --- a/src/utils.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "utils.h" -#include "glib_compat.h" -#include "conf.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <sys/types.h> -#include <fcntl.h> -#include <errno.h> - -#ifndef WIN32 -#include <pwd.h> -#endif - -#if HAVE_IPV6 && WIN32 -#include <winsock2.h> -#endif - -#if HAVE_IPV6 && ! WIN32 -#include <sys/socket.h> -#endif - -#ifdef WIN32 -#include <windows.h> -#endif - -G_GNUC_CONST -static inline GQuark -parse_path_quark(void) -{ - return g_quark_from_static_string("path"); -} - -char * -parsePath(const char *path, G_GNUC_UNUSED GError **error_r) -{ - assert(path != NULL); - assert(error_r == NULL || *error_r == NULL); - -#ifndef WIN32 - if (!g_path_is_absolute(path) && path[0] != '~') { - g_set_error(error_r, parse_path_quark(), 0, - "not an absolute path: %s", path); - return NULL; - } else if (path[0] == '~') { - const char *home; - - if (path[1] == '/' || path[1] == '\0') { - const char *user = config_get_string(CONF_USER, NULL); - if (user != NULL) { - struct passwd *passwd = getpwnam(user); - if (!passwd) { - g_set_error(error_r, parse_path_quark(), 0, - "no such user: %s", user); - return NULL; - } - - home = passwd->pw_dir; - } else { - home = g_get_home_dir(); - if (home == NULL) { - g_set_error_literal(error_r, parse_path_quark(), 0, - "problems getting home " - "for current user"); - return NULL; - } - } - - ++path; - } else { - ++path; - - const char *slash = strchr(path, '/'); - char *user = slash != NULL - ? g_strndup(path, slash - path) - : g_strdup(path); - - struct passwd *passwd = getpwnam(user); - if (!passwd) { - g_set_error(error_r, parse_path_quark(), 0, - "no such user: %s", user); - g_free(user); - return NULL; - } - - g_free(user); - - home = passwd->pw_dir; - path = slash; - } - - return g_strconcat(home, path, NULL); - } else { -#endif - return g_strdup(path); -#ifndef WIN32 - } -#endif -} diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index f8d6657f2..000000000 --- a/src/utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UTILS_H -#define MPD_UTILS_H - -#include <glib.h> -#include <stdbool.h> - -#ifndef assert_static -/* Compile time assertion developed by Ralf Holly */ -/* http://pera-software.com/articles/compile-time-assertions.pdf */ -#define assert_static(e) \ - do { \ - enum { assert_static__ = 1/(e) }; \ - } while (0) -#endif /* !assert_static */ - -char * -parsePath(const char *path, GError **error_r); - -#endif diff --git a/src/volume.c b/src/volume.c deleted file mode 100644 index d3ce47dd4..000000000 --- a/src/volume.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "volume.h" -#include "conf.h" -#include "idle.h" -#include "pcm_volume.h" -#include "output_all.h" -#include "mixer_control.h" -#include "mixer_all.h" -#include "mixer_type.h" -#include "event_pipe.h" - -#include <glib.h> - -#include <assert.h> -#include <math.h> -#include <string.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "volume" - -#define SW_VOLUME_STATE "sw_volume: " - -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; - -/** - * Handler for #PIPE_EVENT_MIXER. - */ -static void -mixer_event_callback(void) -{ - /* flush the hardware volume cache */ - last_hardware_volume = -1; - - /* notify clients */ - idle_add(IDLE_MIXER); -} - -void volume_finish(void) -{ - g_timer_destroy(hardware_volume_timer); -} - -void volume_init(void) -{ - hardware_volume_timer = g_timer_new(); - - event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback); -} - -int volume_level_get(void) -{ - assert(hardware_volume_timer != NULL); - - if (last_hardware_volume >= 0 && - g_timer_elapsed(hardware_volume_timer, NULL) < 1.0) - /* throttle access to hardware mixers */ - return last_hardware_volume; - - last_hardware_volume = mixer_all_get_volume(); - g_timer_start(hardware_volume_timer); - return last_hardware_volume; -} - -static bool software_volume_change(unsigned volume) -{ - assert(volume <= 100); - - volume_software_set = volume; - mixer_all_set_software_volume(volume); - - return true; -} - -static bool hardware_volume_change(unsigned volume) -{ - /* reset the cache */ - last_hardware_volume = -1; - - return mixer_all_set_volume(volume); -} - -bool volume_level_change(unsigned volume) -{ - assert(volume <= 100); - - volume_software_set = volume; - - idle_add(IDLE_MIXER); - - return hardware_volume_change(volume); -} - -bool -read_sw_volume_state(const char *line) -{ - char *end = NULL; - long int sv; - - if (!g_str_has_prefix(line, SW_VOLUME_STATE)) - return false; - - 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) -{ - 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 deleted file mode 100644 index b08899a84..000000000 --- a/src/volume.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VOLUME_H -#define MPD_VOLUME_H - -#include <stdbool.h> -#include <stdio.h> - -void volume_init(void); - -void volume_finish(void); - -int volume_level_get(void); - -bool volume_level_change(unsigned volume); - -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 deleted file mode 100644 index f2cc5359b..000000000 --- a/src/zeroconf-avahi.c +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "zeroconf-internal.h" -#include "listen.h" -#include "mpd_error.h" - -#include <glib.h> - -#include <avahi-client/client.h> -#include <avahi-client/publish.h> - -#include <avahi-common/alternative.h> -#include <avahi-common/domain.h> -#include <avahi-common/malloc.h> -#include <avahi-common/error.h> - -#include <avahi-glib/glib-watch.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "avahi" - -static char *avahiName; -static int avahiRunning; -static AvahiGLibPoll *avahi_glib_poll; -static const AvahiPoll *avahi_poll; -static AvahiClient *avahiClient; -static AvahiEntryGroup *avahiGroup; - -static void avahiRegisterService(AvahiClient * c); - -/* Callback when the EntryGroup changes state */ -static void avahiGroupCallback(AvahiEntryGroup * g, - AvahiEntryGroupState state, - G_GNUC_UNUSED void *userdata) -{ - char *n; - assert(g); - - g_debug("Service group changed to state %d", state); - - switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - /* The entry group has been established successfully */ - g_message("Service '%s' successfully established.", - avahiName); - break; - - case AVAHI_ENTRY_GROUP_COLLISION: - /* A service name collision happened. Let's pick a new name */ - n = avahi_alternative_service_name(avahiName); - avahi_free(avahiName); - avahiName = n; - - g_message("Service name collision, renaming service to '%s'", - avahiName); - - /* And recreate the services */ - avahiRegisterService(avahi_entry_group_get_client(g)); - break; - - case AVAHI_ENTRY_GROUP_FAILURE: - g_warning("Entry group failure: %s", - avahi_strerror(avahi_client_errno - (avahi_entry_group_get_client(g)))); - /* Some kind of failure happened while we were registering our services */ - avahiRunning = 0; - break; - - case AVAHI_ENTRY_GROUP_UNCOMMITED: - g_debug("Service group is UNCOMMITED"); - break; - case AVAHI_ENTRY_GROUP_REGISTERING: - g_debug("Service group is REGISTERING"); - } -} - -/* Registers a new service with avahi */ -static void avahiRegisterService(AvahiClient * c) -{ - int ret; - assert(c); - g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName); - - /* If this is the first time we're called, - * let's create a new entry group */ - if (!avahiGroup) { - avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); - if (!avahiGroup) { - g_warning("Failed to create avahi EntryGroup: %s", - avahi_strerror(avahi_client_errno(c))); - goto fail; - } - } - - /* Add the service */ - /* TODO: This currently binds to ALL interfaces. - * We could maybe add a service per actual bound interface, - * if that's better. */ - ret = avahi_entry_group_add_service(avahiGroup, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - 0, avahiName, SERVICE_TYPE, NULL, - NULL, listen_port, NULL); - if (ret < 0) { - g_warning("Failed to add service %s: %s", SERVICE_TYPE, - avahi_strerror(ret)); - goto fail; - } - - /* Tell the server to register the service group */ - ret = avahi_entry_group_commit(avahiGroup); - if (ret < 0) { - g_warning("Failed to commit service group: %s", - avahi_strerror(ret)); - goto fail; - } - return; - -fail: - avahiRunning = 0; -} - -/* Callback when avahi changes state */ -static void avahiClientCallback(AvahiClient * c, AvahiClientState state, - G_GNUC_UNUSED void *userdata) -{ - int reason; - assert(c); - - /* Called whenever the client or server state changes */ - g_debug("Client changed to state %d", state); - - switch (state) { - case AVAHI_CLIENT_S_RUNNING: - g_debug("Client is RUNNING"); - - /* The server has startup successfully and registered its host - * name on the network, so it's time to create our services */ - if (!avahiGroup) - avahiRegisterService(c); - break; - - case AVAHI_CLIENT_FAILURE: - reason = avahi_client_errno(c); - if (reason == AVAHI_ERR_DISCONNECTED) { - g_message("Client Disconnected, will reconnect shortly"); - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = NULL; - } - if (avahiClient) - avahi_client_free(avahiClient); - avahiClient = - avahi_client_new(avahi_poll, - AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, NULL, - &reason); - if (!avahiClient) { - g_warning("Could not reconnect: %s", - avahi_strerror(reason)); - avahiRunning = 0; - } - } else { - g_warning("Client failure: %s (terminal)", - avahi_strerror(reason)); - avahiRunning = 0; - } - break; - - case AVAHI_CLIENT_S_COLLISION: - g_debug("Client is COLLISION"); - /* Let's drop our registered services. When the server is back - * in AVAHI_SERVER_RUNNING state we will register them - * again with the new host name. */ - if (avahiGroup) { - g_debug("Resetting group"); - avahi_entry_group_reset(avahiGroup); - } - - case AVAHI_CLIENT_S_REGISTERING: - g_debug("Client is REGISTERING"); - /* The server records are now being established. This - * might be caused by a host name change. We need to wait - * for our own records to register until the host name is - * properly esatblished. */ - - if (avahiGroup) { - g_debug("Resetting group"); - avahi_entry_group_reset(avahiGroup); - } - - break; - - case AVAHI_CLIENT_CONNECTING: - g_debug("Client is CONNECTING"); - } -} - -void init_avahi(const char *serviceName) -{ - int error; - g_debug("Initializing interface"); - - if (!avahi_is_valid_service_name(serviceName)) - MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName); - - avahiName = avahi_strdup(serviceName); - - avahiRunning = 1; - - avahi_glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); - avahi_poll = avahi_glib_poll_get(avahi_glib_poll); - - avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, NULL, &error); - - if (!avahiClient) { - g_warning("Failed to create client: %s", - avahi_strerror(error)); - goto fail; - } - - return; - -fail: - avahi_finish(); -} - -void avahi_finish(void) -{ - g_debug("Shutting down interface"); - - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = NULL; - } - - if (avahiClient) { - avahi_client_free(avahiClient); - avahiClient = NULL; - } - - if (avahi_glib_poll != NULL) { - avahi_glib_poll_free(avahi_glib_poll); - avahi_glib_poll = NULL; - } - - avahi_free(avahiName); - avahiName = NULL; -} diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c deleted file mode 100644 index 0f216aade..000000000 --- a/src/zeroconf-bonjour.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "zeroconf-internal.h" -#include "listen.h" - -#include <glib.h> - -#include <dns_sd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "bonjour" - -static DNSServiceRef dnsReference; -static GIOChannel *bonjour_channel; - -static void -dnsRegisterCallback(G_GNUC_UNUSED DNSServiceRef sdRef, - G_GNUC_UNUSED DNSServiceFlags flags, - DNSServiceErrorType errorCode, const char *name, - G_GNUC_UNUSED const char *regtype, - G_GNUC_UNUSED const char *domain, - G_GNUC_UNUSED void *context) -{ - if (errorCode != kDNSServiceErr_NoError) { - g_warning("Failed to register zeroconf service."); - - bonjour_finish(); - } else { - g_debug("Registered zeroconf service with name '%s'", name); - } -} - -static gboolean -bonjour_channel_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) -{ - DNSServiceProcessResult(dnsReference); - - return dnsReference != NULL; -} - -void init_zeroconf_osx(const char *serviceName) -{ - DNSServiceErrorType error = DNSServiceRegister(&dnsReference, - 0, 0, serviceName, - SERVICE_TYPE, NULL, NULL, - g_htons(listen_port), 0, - NULL, - dnsRegisterCallback, - NULL); - - if (error != kDNSServiceErr_NoError) { - g_warning("Failed to register zeroconf service."); - - if (dnsReference) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = NULL; - } - return; - } - - bonjour_channel = g_io_channel_unix_new(DNSServiceRefSockFD(dnsReference)); - g_io_add_watch(bonjour_channel, G_IO_IN, bonjour_channel_event, NULL); -} - -void bonjour_finish(void) -{ - if (bonjour_channel != NULL) { - g_io_channel_unref(bonjour_channel); - bonjour_channel = NULL; - } - - if (dnsReference != NULL) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = NULL; - g_debug("Deregistered Zeroconf service."); - } -} diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h deleted file mode 100644 index 983e5c556..000000000 --- a/src/zeroconf-internal.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ZEROCONF_INTERNAL_H -#define ZEROCONF_INTERNAL_H - -/* The dns-sd service type qualifier to publish */ -#define SERVICE_TYPE "_mpd._tcp" - -void init_avahi(const char *service_name); - -void avahi_finish(void); - -void init_zeroconf_osx(const char *service_name); - -void bonjour_finish(void); - -#endif diff --git a/src/zeroconf.c b/src/zeroconf.c deleted file mode 100644 index 4a399e4a2..000000000 --- a/src/zeroconf.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "zeroconf.h" -#include "zeroconf-internal.h" -#include "conf.h" -#include "listen.h" - -#include <glib.h> - -/* The default service name to publish - * (overridden by 'zeroconf_name' config parameter) - */ -#define SERVICE_NAME "Music Player" - -#define DEFAULT_ZEROCONF_ENABLED 1 - -static int zeroconfEnabled; - -void initZeroconf(void) -{ - const char *serviceName; - - zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED, - DEFAULT_ZEROCONF_ENABLED); - if (!zeroconfEnabled) - return; - - if (listen_port <= 0) { - g_warning("No global port, disabling zeroconf"); - zeroconfEnabled = false; - return; - } - - serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); - -#ifdef HAVE_AVAHI - init_avahi(serviceName); -#endif - -#ifdef HAVE_BONJOUR - init_zeroconf_osx(serviceName); -#endif -} - -void finishZeroconf(void) -{ - if (!zeroconfEnabled) - return; - -#ifdef HAVE_AVAHI - avahi_finish(); -#endif /* HAVE_AVAHI */ - -#ifdef HAVE_BONJOUR - bonjour_finish(); -#endif -} diff --git a/src/zeroconf.h b/src/zeroconf.h deleted file mode 100644 index 8e33a3d89..000000000 --- a/src/zeroconf.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ZEROCONF_H -#define MPD_ZEROCONF_H - -#include "check.h" - -#ifdef HAVE_ZEROCONF - -void initZeroconf(void); -void finishZeroconf(void); - -#else /* ! HAVE_ZEROCONF */ - -static void initZeroconf(void) { } -static void finishZeroconf(void) { } - -#endif /* ! HAVE_ZEROCONF */ - -#endif diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx new file mode 100644 index 000000000..395ff4f23 --- /dev/null +++ b/test/DumpDatabase.cxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseRegistry.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "PlaylistVector.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigData.hxx" +#include "tag/TagConfig.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <iostream> +using std::cout; +using std::cerr; +using std::endl; + +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static bool +DumpDirectory(const Directory &directory, Error &) +{ + cout << "D " << directory.path << endl; + return true; +} + +static bool +DumpSong(Song &song, Error &) +{ + cout << "S " << song.parent->path << "/" << song.uri << endl; + return true; +} + +static bool +DumpPlaylist(const PlaylistInfo &playlist, + const Directory &directory, Error &) +{ + cout << "P " << directory.path << "/" << playlist.name.c_str() << endl; + return true; +} + +int +main(int argc, char **argv) +{ + if (argc != 3) { + cerr << "Usage: DumpDatabase CONFIG PLUGIN" << endl; + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + const char *const plugin_name = argv[2]; + + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == NULL) { + cerr << "No such database plugin: " << plugin_name << endl; + return EXIT_FAILURE; + } + + /* initialize GLib */ + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(nullptr); +#endif + + g_log_set_default_handler(my_log_func, nullptr); + + /* initialize MPD */ + + config_global_init(); + + Error error; + if (!ReadConfigFile(config_path, error)) { + cerr << error.GetMessage() << endl; + return EXIT_FAILURE; + } + + TagLoadConfig(); + + /* do it */ + + const struct config_param *path = config_get_param(CONF_DB_FILE); + config_param param("database", path->line); + if (path != nullptr) + param.AddBlockParam("path", path->value, path->line); + + Database *db = plugin->create(param, error); + + if (db == nullptr) { + cerr << error.GetMessage() << endl; + return EXIT_FAILURE; + } + + if (!db->Open(error)) { + delete db; + cerr << error.GetMessage() << endl; + return EXIT_FAILURE; + } + + const DatabaseSelection selection("", true); + + if (!db->Visit(selection, DumpDirectory, DumpSong, DumpPlaylist, + error)) { + db->Close(); + delete db; + cerr << error.GetMessage() << endl; + return EXIT_FAILURE; + } + + db->Close(); + delete db; + + /* deinitialize everything */ + + config_global_finish(); + + return EXIT_SUCCESS; +} diff --git a/test/FakeReplayGainConfig.cxx b/test/FakeReplayGainConfig.cxx new file mode 100644 index 000000000..3305b79a3 --- /dev/null +++ b/test/FakeReplayGainConfig.cxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ReplayGainConfig.hxx" + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit = true; diff --git a/test/FakeSong.cxx b/test/FakeSong.cxx new file mode 100644 index 000000000..ef7879f1b --- /dev/null +++ b/test/FakeSong.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Song.hxx" +#include "directory.h" +#include "gcc.h" + +#include <stdlib.h> + +struct directory detached_root; + +Song * +song_dup_detached(gcc_unused const Song *src) +{ + abort(); +} diff --git a/test/dump_playlist.c b/test/dump_playlist.c deleted file mode 100644 index 84ac69045..000000000 --- a/test/dump_playlist.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" -#include "conf.h" -#include "song.h" -#include "decoder_api.h" -#include "decoder_list.h" -#include "playlist_list.h" -#include "playlist_plugin.h" - -#include <glib.h> - -#include <unistd.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -void -decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED bool seekable, - G_GNUC_UNUSED float total_time) -{ -} - -enum decoder_command -decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) -{ - return DECODE_COMMAND_NONE; -} - -void -decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -double -decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) -{ - return 1.0; -} - -void -decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -size_t -decoder_read(G_GNUC_UNUSED struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - return input_stream_lock_read(is, buffer, length, NULL); -} - -void -decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED double t) -{ -} - -enum decoder_command -decoder_data(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - const void *data, size_t datalen, - G_GNUC_UNUSED uint16_t kbit_rate) -{ - G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED const struct tag *tag) -{ - return DECODE_COMMAND_NONE; -} - -float -decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, - const struct replay_gain_info *replay_gain_info) -{ - const struct replay_gain_tuple *tuple = - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - return 0.0; -} - -void -decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - g_free(mixramp_start); - g_free(mixramp_end); -} - -int main(int argc, char **argv) -{ - const char *uri; - struct input_stream *is = NULL; - bool success; - GError *error = NULL; - struct playlist_provider *playlist; - struct song *song; - - if (argc != 3) { - g_printerr("Usage: dump_playlist CONFIG URI\n"); - return 1; - } - - uri = argv[2]; - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* initialize MPD */ - - tag_pool_init(); - config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { - g_printerr("%s\n", error->message); - g_error_free(error); - return 1; - } - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - playlist_list_global_init(); - decoder_plugin_init_all(); - - /* open the playlist */ - - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - playlist = playlist_list_open_uri(uri, mutex, cond); - if (playlist == NULL) { - /* open the stream and wait until it becomes ready */ - - is = input_stream_open(uri, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - return 2; - } - - input_stream_lock_wait_ready(is); - - /* open the playlist */ - - playlist = playlist_list_open_stream(is, uri); - if (playlist == NULL) { - input_stream_close(is); - g_printerr("Failed to open playlist\n"); - return 2; - } - } - - /* dump the playlist */ - - while ((song = playlist_plugin_read(playlist)) != NULL) { - g_print("%s\n", song->uri); - - if (song->end_ms > 0) - g_print("range: %u:%02u..%u:%02u\n", - song->start_ms / 60000, - (song->start_ms / 1000) % 60, - song->end_ms / 60000, - (song->end_ms / 1000) % 60); - else if (song->start_ms > 0) - g_print("range: %u:%02u..\n", - song->start_ms / 60000, - (song->start_ms / 1000) % 60); - - if (song->tag != NULL) - tag_save(stdout, song->tag); - - song_free(song); - } - - /* deinitialize everything */ - - playlist_plugin_close(playlist); - if (is != NULL) - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - - decoder_plugin_deinit_all(); - playlist_list_global_finish(); - input_stream_global_finish(); - io_thread_deinit(); - config_global_finish(); - tag_pool_deinit(); - - return 0; -} diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx new file mode 100644 index 000000000..2f6b3ed2a --- /dev/null +++ b/test/dump_playlist.cxx @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagSave.hxx" +#include "Song.hxx" +#include "SongEnumerator.hxx" +#include "Directory.hxx" +#include "InputStream.hxx" +#include "ConfigGlobal.hxx" +#include "DecoderAPI.hxx" +#include "DecoderList.hxx" +#include "InputInit.hxx" +#include "IOThread.hxx" +#include "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> + +Directory::Directory() {} +Directory::~Directory() {} + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +void +decoder_initialized(gcc_unused struct decoder *decoder, + gcc_unused const AudioFormat audio_format, + gcc_unused bool seekable, + gcc_unused float total_time) +{ +} + +DecoderCommand +decoder_get_command(gcc_unused struct decoder *decoder) +{ + return DecoderCommand::NONE; +} + +void +decoder_command_finished(gcc_unused struct decoder *decoder) +{ +} + +double +decoder_seek_where(gcc_unused struct decoder *decoder) +{ + return 1.0; +} + +void +decoder_seek_error(gcc_unused struct decoder *decoder) +{ +} + +size_t +decoder_read(gcc_unused struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + Error error; + return is->LockRead(buffer, length, error); +} + +void +decoder_timestamp(gcc_unused struct decoder *decoder, + gcc_unused double t) +{ +} + +DecoderCommand +decoder_data(gcc_unused struct decoder *decoder, + gcc_unused struct input_stream *is, + const void *data, size_t datalen, + gcc_unused uint16_t kbit_rate) +{ + gcc_unused ssize_t nbytes = write(1, data, datalen); + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(gcc_unused struct decoder *decoder, + gcc_unused struct input_stream *is, + gcc_unused Tag &&tag) +{ + return DecoderCommand::NONE; +} + +void +decoder_replay_gain(gcc_unused struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + const struct replay_gain_tuple *tuple = + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); +} + +void +decoder_mixramp(gcc_unused struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + +int main(int argc, char **argv) +{ + const char *uri; + struct input_stream *is = NULL; + Song *song; + + if (argc != 3) { + g_printerr("Usage: dump_playlist CONFIG URI\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + uri = argv[2]; + + /* initialize GLib */ + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + Error error; + if (!ReadConfigFile(config_path, error)) { + g_printerr("%s\n", error.GetMessage()); + return 1; + } + + io_thread_init(); + io_thread_start(); + + if (!input_stream_global_init(error)) { + LogError(error); + return 2; + } + + playlist_list_global_init(); + decoder_plugin_init_all(); + + /* open the playlist */ + + Mutex mutex; + Cond cond; + + auto playlist = playlist_list_open_uri(uri, mutex, cond); + if (playlist == NULL) { + /* open the stream and wait until it becomes ready */ + + is = input_stream::Open(uri, mutex, cond, error); + if (is == NULL) { + if (error.IsDefined()) + LogError(error); + else + g_printerr("input_stream::Open() failed\n"); + return 2; + } + + is->LockWaitReady(); + + /* open the playlist */ + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + is->Close(); + g_printerr("Failed to open playlist\n"); + return 2; + } + } + + /* dump the playlist */ + + while ((song = playlist->NextSong()) != NULL) { + g_print("%s\n", song->uri); + + if (song->end_ms > 0) + g_print("range: %u:%02u..%u:%02u\n", + song->start_ms / 60000, + (song->start_ms / 1000) % 60, + song->end_ms / 60000, + (song->end_ms / 1000) % 60); + else if (song->start_ms > 0) + g_print("range: %u:%02u..\n", + song->start_ms / 60000, + (song->start_ms / 1000) % 60); + + if (song->tag != NULL) + tag_save(stdout, *song->tag); + + song->Free(); + } + + /* deinitialize everything */ + + delete playlist; + if (is != NULL) + is->Close(); + + decoder_plugin_deinit_all(); + playlist_list_global_finish(); + input_stream_global_finish(); + io_thread_deinit(); + config_global_finish(); + + return 0; +} diff --git a/test/dump_rva2.c b/test/dump_rva2.c deleted file mode 100644 index 4a726518a..000000000 --- a/test/dump_rva2.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "tag_id3.h" -#include "tag_rva2.h" -#include "replay_gain_info.h" -#include "conf.h" -#include "tag.h" - -#include <id3tag.h> - -#ifdef HAVE_LOCALE_H -#include <locale.h> -#endif - -#include <stdlib.h> - -const char * -config_get_string(G_GNUC_UNUSED const char *name, const char *default_value) -{ - return default_value; -} - -struct tag * -tag_new(void) -{ - return NULL; -} - -void -tag_add_item_n(G_GNUC_UNUSED struct tag *tag, G_GNUC_UNUSED enum tag_type type, - G_GNUC_UNUSED const char *value, G_GNUC_UNUSED size_t len) -{ -} - -void -tag_free(struct tag *tag) -{ - g_free(tag); -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - -#ifdef HAVE_LOCALE_H - /* initialize locale */ - setlocale(LC_CTYPE,""); -#endif - - if (argc != 2) { - g_printerr("Usage: read_rva2 FILE\n"); - return 1; - } - - const char *path = argv[1]; - - struct id3_tag *tag = tag_id3_load(path, &error); - if (tag == NULL) { - if (error != NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - } else - g_printerr("No ID3 tag found\n"); - - return EXIT_FAILURE; - } - - struct replay_gain_info replay_gain; - replay_gain_info_init(&replay_gain); - - bool success = tag_rva2_parse(tag, &replay_gain); - id3_tag_delete(tag); - - if (!success) { - g_printerr("No RVA2 tag found\n"); - return EXIT_FAILURE; - } - - const struct replay_gain_tuple *tuple = - &replay_gain.tuples[REPLAY_GAIN_ALBUM]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - return EXIT_SUCCESS; -} diff --git a/test/dump_rva2.cxx b/test/dump_rva2.cxx new file mode 100644 index 000000000..3206f970a --- /dev/null +++ b/test/dump_rva2.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/TagId3.hxx" +#include "tag/TagRva2.hxx" +#include "ReplayGainInfo.hxx" +#include "ConfigGlobal.hxx" +#include "util/Error.hxx" + +#include <id3tag.h> + +#include <glib.h> + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include <stdlib.h> + +const char * +config_get_string(gcc_unused enum ConfigOption option, + const char *default_value) +{ + return default_value; +} + +int main(int argc, char **argv) +{ +#ifdef HAVE_LOCALE_H + /* initialize locale */ + setlocale(LC_CTYPE,""); +#endif + + if (argc != 2) { + g_printerr("Usage: read_rva2 FILE\n"); + return 1; + } + + const char *path = argv[1]; + + Error error; + struct id3_tag *tag = tag_id3_load(path, error); + if (tag == NULL) { + if (error.IsDefined()) + g_printerr("%s\n", error.GetMessage()); + else + g_printerr("No ID3 tag found\n"); + + return EXIT_FAILURE; + } + + struct replay_gain_info replay_gain; + replay_gain_info_init(&replay_gain); + + bool success = tag_rva2_parse(tag, &replay_gain); + id3_tag_delete(tag); + + if (!success) { + g_printerr("No RVA2 tag found\n"); + return EXIT_FAILURE; + } + + const struct replay_gain_tuple *tuple = + &replay_gain.tuples[REPLAY_GAIN_ALBUM]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + return EXIT_SUCCESS; +} diff --git a/test/dump_text_file.c b/test/dump_text_file.c deleted file mode 100644 index f14371441..000000000 --- a/test/dump_text_file.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "text_input_stream.h" -#include "tag_pool.h" -#include "conf.h" -#include "stdbin.h" - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#endif - -#include <glib.h> - -#include <unistd.h> -#include <stdio.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -static void -dump_text_file(struct text_input_stream *is) -{ - const char *line; - while ((line = text_input_stream_read(is)) != NULL) - printf("'%s'\n", line); -} - -static int -dump_input_stream(struct input_stream *is) -{ - GError *error = NULL; - - input_stream_lock(is); - - /* wait until the stream becomes ready */ - - input_stream_wait_ready(is); - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - /* read data and tags from the stream */ - - input_stream_unlock(is); - - struct text_input_stream *tis = text_input_stream_new(is); - dump_text_file(tis); - text_input_stream_free(tis); - - input_stream_lock(is); - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - input_stream_unlock(is); - - return 0; -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct input_stream *is; - int ret; - - if (argc != 2) { - g_printerr("Usage: run_input URI\n"); - return 1; - } - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* initialize MPD */ - - tag_pool_init(); - config_global_init(); - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - -#ifdef ENABLE_ARCHIVE - archive_plugin_init_all(); -#endif - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - /* open the stream and dump it */ - - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - is = input_stream_open(argv[1], mutex, cond, &error); - if (is != NULL) { - ret = dump_input_stream(is); - input_stream_close(is); - } else { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - ret = 2; - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - input_stream_global_finish(); - -#ifdef ENABLE_ARCHIVE - archive_plugin_deinit_all(); -#endif - - io_thread_deinit(); - - config_global_finish(); - tag_pool_deinit(); - - return ret; -} diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx new file mode 100644 index 000000000..685f0fbb9 --- /dev/null +++ b/test/dump_text_file.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "IOThread.hxx" +#include "InputInit.hxx" +#include "InputStream.hxx" +#include "ConfigGlobal.hxx" +#include "stdbin.h" +#include "TextInputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static void +dump_text_file(TextInputStream &is) +{ + std::string line; + while (is.ReadLine(line)) + printf("'%s'\n", line.c_str()); +} + +static int +dump_input_stream(struct input_stream *is) +{ + Error error; + + is->Lock(); + + /* wait until the stream becomes ready */ + + is->WaitReady(); + + if (!is->Check(error)) { + LogError(error); + is->Unlock(); + return EXIT_FAILURE; + } + + /* read data and tags from the stream */ + + is->Unlock(); + { + TextInputStream tis(is); + dump_text_file(tis); + } + + is->Lock(); + + if (!is->Check(error)) { + LogError(error); + is->Unlock(); + return EXIT_FAILURE; + } + + is->Unlock(); + + return 0; +} + +int main(int argc, char **argv) +{ + struct input_stream *is; + int ret; + + if (argc != 2) { + g_printerr("Usage: run_input URI\n"); + return 1; + } + + /* initialize GLib */ + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + io_thread_init(); + io_thread_start(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + Error error; + if (!input_stream_global_init(error)) { + LogError(error); + return 2; + } + + /* open the stream and dump it */ + + Mutex mutex; + Cond cond; + + is = input_stream::Open(argv[1], mutex, cond, error); + if (is != NULL) { + ret = dump_input_stream(is); + is->Close(); + } else { + if (error.IsDefined()) + LogError(error); + else + g_printerr("input_stream::Open() failed\n"); + ret = 2; + } + + /* deinitialize everything */ + + input_stream_global_finish(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + + io_thread_deinit(); + + config_global_finish(); + + return ret; +} diff --git a/test/read_conf.c b/test/read_conf.c deleted file mode 100644 index 4f6005c6f..000000000 --- a/test/read_conf.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" - -#include <glib.h> - -#include <assert.h> - -static void -my_log_func(G_GNUC_UNUSED const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_level > G_LOG_LEVEL_WARNING) - return; - - g_printerr("%s\n", message); -} - -int main(int argc, char **argv) -{ - if (argc != 3) { - g_printerr("Usage: read_conf FILE SETTING\n"); - return 1; - } - - const char *path = argv[1]; - const char *name = argv[2]; - - g_log_set_default_handler(my_log_func, NULL); - - config_global_init(); - - GError *error = NULL; - bool success = config_read_file(path, &error); - if (!success) { - g_printerr("%s:", error->message); - g_error_free(error); - return 1; - } - - const char *value = config_get_string(name, NULL); - int ret; - if (value != NULL) { - g_print("%s\n", value); - ret = 0; - } else { - g_printerr("No such setting: %s\n", name); - ret = 2; - } - - config_global_finish(); - return ret; -} diff --git a/test/read_conf.cxx b/test/read_conf.cxx new file mode 100644 index 000000000..d5eacec67 --- /dev/null +++ b/test/read_conf.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigGlobal.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <assert.h> + +static void +my_log_func(gcc_unused const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_level > G_LOG_LEVEL_WARNING) + return; + + g_printerr("%s\n", message); +} + +int main(int argc, char **argv) +{ + if (argc != 3) { + g_printerr("Usage: read_conf FILE SETTING\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + const char *name = argv[2]; + + g_log_set_default_handler(my_log_func, NULL); + + config_global_init(); + + Error error; + if (!ReadConfigFile(config_path, error)) { + g_printerr("%s:", error.GetMessage()); + return 1; + } + + ConfigOption option = ParseConfigOptionName(name); + const char *value = option != CONF_MAX + ? config_get_string(option, nullptr) + : nullptr; + int ret; + if (value != NULL) { + g_print("%s\n", value); + ret = 0; + } else { + g_printerr("No such setting: %s\n", name); + ret = 2; + } + + config_global_finish(); + return ret; +} diff --git a/test/read_mixer.c b/test/read_mixer.c deleted file mode 100644 index f6de8177d..000000000 --- a/test/read_mixer.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "mixer_control.h" -#include "mixer_list.h" -#include "filter_registry.h" -#include "pcm_volume.h" -#include "event_pipe.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <unistd.h> - -#ifdef HAVE_PULSE -#include "output/pulse_output_plugin.h" - -void -pulse_output_lock(G_GNUC_UNUSED struct pulse_output *po) -{ -} - -void -pulse_output_unlock(G_GNUC_UNUSED struct pulse_output *po) -{ -} - -void -pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po, - G_GNUC_UNUSED struct pulse_mixer *pm) -{ -} - -void -pulse_output_clear_mixer(G_GNUC_UNUSED struct pulse_output *po, - G_GNUC_UNUSED struct pulse_mixer *pm) -{ -} - -bool -pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, - G_GNUC_UNUSED const struct pa_cvolume *volume, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -#endif - -#ifdef HAVE_ROAR -#include "output/roar_output_plugin.h" - -int -roar_output_get_volume(G_GNUC_UNUSED struct roar *roar) -{ - return -1; -} - -bool -roar_output_set_volume(G_GNUC_UNUSED struct roar *roar, - G_GNUC_UNUSED unsigned volume) -{ - return true; -} - -#endif - -void -event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) -{ -} - -const struct filter_plugin * -filter_plugin_by_name(G_GNUC_UNUSED const char *name) -{ - assert(false); - return NULL; -} - -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - assert(false); - return false; -} - -int main(int argc, G_GNUC_UNUSED char **argv) -{ - GError *error = NULL; - struct mixer *mixer; - bool success; - int volume; - - if (argc != 2) { - g_printerr("Usage: read_mixer PLUGIN\n"); - return 1; - } - - g_thread_init(NULL); - - mixer = mixer_new(&alsa_mixer_plugin, NULL, NULL, &error); - if (mixer == NULL) { - g_printerr("mixer_new() failed: %s\n", error->message); - g_error_free(error); - return 2; - } - - success = mixer_open(mixer, &error); - if (!success) { - mixer_free(mixer); - g_printerr("failed to open the mixer: %s\n", error->message); - g_error_free(error); - return 2; - } - - volume = mixer_get_volume(mixer, &error); - mixer_close(mixer); - mixer_free(mixer); - - assert(volume >= -1 && volume <= 100); - - if (volume < 0) { - if (error != NULL) { - g_printerr("failed to read volume: %s\n", - error->message); - g_error_free(error); - } else - g_printerr("failed to read volume\n"); - return 2; - } - - g_print("%d\n", volume); - return 0; -} diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx new file mode 100644 index 000000000..8426443ae --- /dev/null +++ b/test/read_mixer.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MixerControl.hxx" +#include "MixerList.hxx" +#include "FilterRegistry.hxx" +#include "pcm/PcmVolume.hxx" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "ConfigData.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> + +EventLoop *main_loop; + +#ifdef HAVE_PULSE +#include "output/PulseOutputPlugin.hxx" + +void +pulse_output_lock(gcc_unused PulseOutput *po) +{ +} + +void +pulse_output_unlock(gcc_unused PulseOutput *po) +{ +} + +void +pulse_output_set_mixer(gcc_unused PulseOutput *po, + gcc_unused PulseMixer *pm) +{ +} + +void +pulse_output_clear_mixer(gcc_unused PulseOutput *po, + gcc_unused PulseMixer *pm) +{ +} + +bool +pulse_output_set_volume(gcc_unused PulseOutput *po, + gcc_unused const struct pa_cvolume *volume, + gcc_unused Error &error) +{ + return false; +} + +#endif + +#ifdef HAVE_ROAR +#include "output/RoarOutputPlugin.hxx" + +int +roar_output_get_volume(gcc_unused RoarOutput *roar) +{ + return -1; +} + +bool +roar_output_set_volume(gcc_unused RoarOutput *roar, + gcc_unused unsigned volume) +{ + return true; +} + +#endif + +void +GlobalEvents::Emit(gcc_unused Event event) +{ +} + +const struct filter_plugin * +filter_plugin_by_name(gcc_unused const char *name) +{ + assert(false); + return NULL; +} + +bool +pcm_volume(gcc_unused void *buffer, gcc_unused size_t length, + gcc_unused SampleFormat format, + gcc_unused int volume) +{ + assert(false); + return false; +} + +int main(int argc, gcc_unused char **argv) +{ + int volume; + + if (argc != 2) { + g_printerr("Usage: read_mixer PLUGIN\n"); + return 1; + } + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + main_loop = new EventLoop(EventLoop::Default()); + + Error error; + Mixer *mixer = mixer_new(&alsa_mixer_plugin, nullptr, + config_param(), error); + if (mixer == NULL) { + g_printerr("mixer_new() failed: %s\n", error.GetMessage()); + return 2; + } + + if (!mixer_open(mixer, error)) { + mixer_free(mixer); + g_printerr("failed to open the mixer: %s\n", error.GetMessage()); + return 2; + } + + volume = mixer_get_volume(mixer, error); + mixer_close(mixer); + mixer_free(mixer); + + delete main_loop; + + assert(volume >= -1 && volume <= 100); + + if (volume < 0) { + if (error.IsDefined()) { + g_printerr("failed to read volume: %s\n", + error.GetMessage()); + } else + g_printerr("failed to read volume\n"); + return 2; + } + + g_print("%d\n", volume); + return 0; +} diff --git a/test/read_tags.c b/test/read_tags.c deleted file mode 100644 index faf9a45c0..000000000 --- a/test/read_tags.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "decoder_list.h" -#include "decoder_api.h" -#include "input_init.h" -#include "input_stream.h" -#include "audio_format.h" -#include "pcm_volume.h" -#include "tag_ape.h" -#include "tag_id3.h" -#include "tag_handler.h" -#include "idle.h" - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> - -#ifdef HAVE_LOCALE_H -#include <locale.h> -#endif - -/** - * No-op dummy. - */ -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -/** - * No-op dummy. - */ -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - return true; -} - -void -decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED bool seekable, - G_GNUC_UNUSED float total_time) -{ -} - -enum decoder_command -decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) -{ - return DECODE_COMMAND_NONE; -} - -void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) -{ - return 1.0; -} - -void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -size_t -decoder_read(G_GNUC_UNUSED struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - return input_stream_lock_read(is, buffer, length, NULL); -} - -void -decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED double t) -{ -} - -enum decoder_command -decoder_data(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - const void *data, size_t datalen, - G_GNUC_UNUSED uint16_t bit_rate) -{ - G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED const struct tag *tag) -{ - return DECODE_COMMAND_NONE; -} - -float -decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info) -{ - return 0.0; -} - -void -decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - g_free(mixramp_start); - g_free(mixramp_end); -} - -static bool empty = true; - -static void -print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx) -{ - g_print("duration=%d\n", seconds); -} - -static void -print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx) -{ - g_print("[%s]=%s\n", tag_item_names[type], value); - empty = false; -} - -static void -print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx) -{ - g_print("\"%s\"=%s\n", name, value); -} - -static const struct tag_handler print_handler = { - .duration = print_duration, - .tag = print_tag, - .pair = print_pair, -}; - -int main(int argc, char **argv) -{ - GError *error = NULL; - const char *decoder_name, *path; - const struct decoder_plugin *plugin; - -#ifdef HAVE_LOCALE_H - /* initialize locale */ - setlocale(LC_CTYPE,""); -#endif - - if (argc != 3) { - g_printerr("Usage: read_tags DECODER FILE\n"); - return 1; - } - - decoder_name = argv[1]; - path = argv[2]; - - g_thread_init(NULL); - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - decoder_plugin_init_all(); - - plugin = decoder_plugin_from_name(decoder_name); - if (plugin == NULL) { - g_printerr("No such decoder: %s\n", decoder_name); - return 1; - } - - bool success = decoder_plugin_scan_file(plugin, path, - &print_handler, NULL); - if (!success && plugin->scan_stream != NULL) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is = - input_stream_open(path, mutex, cond, &error); - - if (is == NULL) { - g_printerr("Failed to open %s: %s\n", - path, error->message); - g_error_free(error); - return 1; - } - - success = decoder_plugin_scan_stream(plugin, is, - &print_handler, NULL); - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - } - - decoder_plugin_deinit_all(); - input_stream_global_finish(); - io_thread_deinit(); - - if (!success) { - g_printerr("Failed to read tags\n"); - return 1; - } - - if (empty) { - tag_ape_scan2(path, &print_handler, NULL); - if (empty) - tag_id3_scan(path, &print_handler, NULL); - } - - return 0; -} diff --git a/test/read_tags.cxx b/test/read_tags.cxx new file mode 100644 index 000000000..b4e5b2bb0 --- /dev/null +++ b/test/read_tags.cxx @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IOThread.hxx" +#include "DecoderList.hxx" +#include "DecoderAPI.hxx" +#include "InputInit.hxx" +#include "InputStream.hxx" +#include "AudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagId3.hxx" +#include "tag/ApeTag.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +void +decoder_initialized(gcc_unused struct decoder *decoder, + gcc_unused const AudioFormat audio_format, + gcc_unused bool seekable, + gcc_unused float total_time) +{ +} + +DecoderCommand +decoder_get_command(gcc_unused struct decoder *decoder) +{ + return DecoderCommand::NONE; +} + +void decoder_command_finished(gcc_unused struct decoder *decoder) +{ +} + +double decoder_seek_where(gcc_unused struct decoder *decoder) +{ + return 1.0; +} + +void decoder_seek_error(gcc_unused struct decoder *decoder) +{ +} + +size_t +decoder_read(gcc_unused struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + Error error; + return is->LockRead(buffer, length, error); +} + +void +decoder_timestamp(gcc_unused struct decoder *decoder, + gcc_unused double t) +{ +} + +DecoderCommand +decoder_data(gcc_unused struct decoder *decoder, + gcc_unused struct input_stream *is, + const void *data, size_t datalen, + gcc_unused uint16_t bit_rate) +{ + gcc_unused ssize_t nbytes = write(1, data, datalen); + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(gcc_unused struct decoder *decoder, + gcc_unused struct input_stream *is, + gcc_unused Tag &&tag) +{ + return DecoderCommand::NONE; +} + +void +decoder_replay_gain(gcc_unused struct decoder *decoder, + gcc_unused const struct replay_gain_info *replay_gain_info) +{ +} + +void +decoder_mixramp(gcc_unused struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + +static bool empty = true; + +static void +print_duration(unsigned seconds, gcc_unused void *ctx) +{ + g_print("duration=%d\n", seconds); +} + +static void +print_tag(enum tag_type type, const char *value, gcc_unused void *ctx) +{ + g_print("[%s]=%s\n", tag_item_names[type], value); + empty = false; +} + +static void +print_pair(const char *name, const char *value, gcc_unused void *ctx) +{ + g_print("\"%s\"=%s\n", name, value); +} + +static const struct tag_handler print_handler = { + print_duration, + print_tag, + print_pair, +}; + +int main(int argc, char **argv) +{ + const char *decoder_name, *path; + const struct decoder_plugin *plugin; + +#ifdef HAVE_LOCALE_H + /* initialize locale */ + setlocale(LC_CTYPE,""); +#endif + + if (argc != 3) { + g_printerr("Usage: read_tags DECODER FILE\n"); + return 1; + } + + decoder_name = argv[1]; + path = argv[2]; + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + io_thread_init(); + io_thread_start(); + + Error error; + if (!input_stream_global_init(error)) { + LogError(error); + return 2; + } + + decoder_plugin_init_all(); + + plugin = decoder_plugin_from_name(decoder_name); + if (plugin == NULL) { + g_printerr("No such decoder: %s\n", decoder_name); + return 1; + } + + bool success = decoder_plugin_scan_file(plugin, path, + &print_handler, NULL); + if (!success && plugin->scan_stream != NULL) { + Mutex mutex; + Cond cond; + + input_stream *is = input_stream::Open(path, mutex, cond, + error); + if (is == NULL) { + g_printerr("Failed to open %s: %s\n", + path, error.GetMessage()); + return 1; + } + + mutex.lock(); + + is->WaitReady(); + + if (!is->Check(error)) { + mutex.unlock(); + + g_printerr("Failed to read %s: %s\n", + path, error.GetMessage()); + return EXIT_FAILURE; + } + + mutex.unlock(); + + success = decoder_plugin_scan_stream(plugin, is, + &print_handler, NULL); + is->Close(); + } + + decoder_plugin_deinit_all(); + input_stream_global_finish(); + io_thread_deinit(); + + if (!success) { + g_printerr("Failed to read tags\n"); + return 1; + } + + if (empty) { + tag_ape_scan2(path, &print_handler, NULL); + if (empty) + tag_id3_scan(path, &print_handler, NULL); + } + + return 0; +} diff --git a/test/run_convert.c b/test/run_convert.c deleted file mode 100644 index 4f57400fd..000000000 --- a/test/run_convert.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This program is a command line interface to MPD's PCM conversion - * library (pcm_convert.c). - * - */ - -#include "config.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "pcm_convert.h" -#include "conf.h" -#include "fifo_buffer.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <stddef.h> -#include <unistd.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -const char * -config_get_string(G_GNUC_UNUSED const char *name, const char *default_value) -{ - return default_value; -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format in_audio_format, out_audio_format; - struct pcm_convert_state state; - const void *output; - ssize_t nbytes; - size_t length; - - if (argc != 3) { - g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n"); - return 1; - } - - g_log_set_default_handler(my_log_func, NULL); - - if (!audio_format_parse(&in_audio_format, argv[1], - false, &error)) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - - struct audio_format out_audio_format_mask; - if (!audio_format_parse(&out_audio_format_mask, argv[2], - true, &error)) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - - out_audio_format = in_audio_format; - audio_format_mask_apply(&out_audio_format, &out_audio_format_mask); - - const size_t in_frame_size = audio_format_frame_size(&in_audio_format); - - pcm_convert_init(&state); - - struct fifo_buffer *buffer = fifo_buffer_new(4096); - - while (true) { - void *p = fifo_buffer_write(buffer, &length); - assert(p != NULL); - - nbytes = read(0, p, length); - if (nbytes <= 0) - break; - - fifo_buffer_append(buffer, nbytes); - - const void *src = fifo_buffer_read(buffer, &length); - assert(src != NULL); - - length -= length % in_frame_size; - if (length == 0) - continue; - - fifo_buffer_consume(buffer, length); - - output = pcm_convert(&state, &in_audio_format, src, length, - &out_audio_format, &length, &error); - if (output == NULL) { - g_printerr("Failed to convert: %s\n", error->message); - return 2; - } - - G_GNUC_UNUSED ssize_t ignored = write(1, output, length); - } - - pcm_convert_deinit(&state); -} diff --git a/test/run_convert.cxx b/test/run_convert.cxx new file mode 100644 index 000000000..939e279d0 --- /dev/null +++ b/test/run_convert.cxx @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This program is a command line interface to MPD's PCM conversion + * library (pcm_convert.c). + * + */ + +#include "config.h" +#include "AudioParser.hxx" +#include "AudioFormat.hxx" +#include "pcm/PcmConvert.hxx" +#include "ConfigGlobal.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <stddef.h> +#include <unistd.h> + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +const char * +config_get_string(gcc_unused enum ConfigOption option, + const char *default_value) +{ + return default_value; +} + +int main(int argc, char **argv) +{ + AudioFormat in_audio_format, out_audio_format; + const void *output; + ssize_t nbytes; + size_t length; + + if (argc != 3) { + g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n"); + return 1; + } + + g_log_set_default_handler(my_log_func, NULL); + + Error error; + if (!audio_format_parse(in_audio_format, argv[1], + false, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + + AudioFormat out_audio_format_mask; + if (!audio_format_parse(out_audio_format_mask, argv[2], + true, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + + out_audio_format = in_audio_format; + out_audio_format.ApplyMask(out_audio_format_mask); + + const size_t in_frame_size = in_audio_format.GetFrameSize(); + + PcmConvert state; + + struct fifo_buffer *buffer = fifo_buffer_new(4096); + + while (true) { + void *p = fifo_buffer_write(buffer, &length); + assert(p != NULL); + + nbytes = read(0, p, length); + if (nbytes <= 0) + break; + + fifo_buffer_append(buffer, nbytes); + + const void *src = fifo_buffer_read(buffer, &length); + assert(src != NULL); + + length -= length % in_frame_size; + if (length == 0) + continue; + + fifo_buffer_consume(buffer, length); + + output = state.Convert(in_audio_format, src, length, + out_audio_format, &length, error); + if (output == NULL) { + g_printerr("Failed to convert: %s\n", error.GetMessage()); + return 2; + } + + gcc_unused ssize_t ignored = write(1, output, length); + } + + return EXIT_SUCCESS; +} diff --git a/test/run_decoder.c b/test/run_decoder.c deleted file mode 100644 index e6712c75b..000000000 --- a/test/run_decoder.c +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "decoder_list.h" -#include "decoder_api.h" -#include "tag_pool.h" -#include "input_init.h" -#include "input_stream.h" -#include "audio_format.h" -#include "pcm_volume.h" -#include "idle.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -/** - * No-op dummy. - */ -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -/** - * No-op dummy. - */ -bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, - G_GNUC_UNUSED enum sample_format format, - G_GNUC_UNUSED int volume) -{ - return true; -} - -struct decoder { - const char *uri; - - const struct decoder_plugin *plugin; - - bool initialized; -}; - -void -decoder_initialized(struct decoder *decoder, - const struct audio_format *audio_format, - G_GNUC_UNUSED bool seekable, - G_GNUC_UNUSED float total_time) -{ - struct audio_format_string af_string; - - assert(!decoder->initialized); - assert(audio_format_valid(audio_format)); - - g_printerr("audio_format=%s\n", - audio_format_to_string(audio_format, &af_string)); - - decoder->initialized = true; -} - -enum decoder_command -decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) -{ - return DECODE_COMMAND_NONE; -} - -void decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -double decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) -{ - return 1.0; -} - -void decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) -{ -} - -size_t -decoder_read(G_GNUC_UNUSED struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - return input_stream_lock_read(is, buffer, length, NULL); -} - -void -decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED double t) -{ -} - -enum decoder_command -decoder_data(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - const void *data, size_t datalen, - G_GNUC_UNUSED uint16_t kbit_rate) -{ - G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED const struct tag *tag) -{ - return DECODE_COMMAND_NONE; -} - -float -decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, - const struct replay_gain_info *replay_gain_info) -{ - const struct replay_gain_tuple *tuple = - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; - if (replay_gain_tuple_defined(tuple)) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - return 0.0; -} - -void -decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, - G_GNUC_UNUSED float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - g_free(mixramp_start); - g_free(mixramp_end); -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - const char *decoder_name; - struct decoder decoder; - - if (argc != 3) { - g_printerr("Usage: run_decoder DECODER URI >OUT\n"); - return 1; - } - - decoder_name = argv[1]; - decoder.uri = argv[2]; - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - tag_pool_init(); - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - decoder_plugin_init_all(); - - decoder.plugin = decoder_plugin_from_name(decoder_name); - if (decoder.plugin == NULL) { - g_printerr("No such decoder: %s\n", decoder_name); - return 1; - } - - decoder.initialized = false; - - if (decoder.plugin->file_decode != NULL) { - decoder_plugin_file_decode(decoder.plugin, &decoder, - decoder.uri); - } else if (decoder.plugin->stream_decode != NULL) { - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - struct input_stream *is = - input_stream_open(decoder.uri, mutex, cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - - return 1; - } - - decoder_plugin_stream_decode(decoder.plugin, &decoder, is); - - input_stream_close(is); - - g_cond_free(cond); - g_mutex_free(mutex); - } else { - g_printerr("Decoder plugin is not usable\n"); - return 1; - } - - decoder_plugin_deinit_all(); - input_stream_global_finish(); - io_thread_deinit(); - - if (!decoder.initialized) { - g_printerr("Decoding failed\n"); - return 1; - } - - tag_pool_deinit(); - - return 0; -} diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx new file mode 100644 index 000000000..c2aa1d7d6 --- /dev/null +++ b/test/run_decoder.cxx @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IOThread.hxx" +#include "DecoderList.hxx" +#include "DecoderAPI.hxx" +#include "InputInit.hxx" +#include "InputStream.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +struct decoder { + const char *uri; + + const struct decoder_plugin *plugin; + + bool initialized; +}; + +void +decoder_initialized(struct decoder *decoder, + const AudioFormat audio_format, + gcc_unused bool seekable, + gcc_unused float total_time) +{ + struct audio_format_string af_string; + + assert(!decoder->initialized); + assert(audio_format.IsValid()); + + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); + + decoder->initialized = true; +} + +DecoderCommand +decoder_get_command(gcc_unused struct decoder *decoder) +{ + return DecoderCommand::NONE; +} + +void decoder_command_finished(gcc_unused struct decoder *decoder) +{ +} + +double decoder_seek_where(gcc_unused struct decoder *decoder) +{ + return 1.0; +} + +void decoder_seek_error(gcc_unused struct decoder *decoder) +{ +} + +size_t +decoder_read(gcc_unused struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + return is->LockRead(buffer, length, IgnoreError()); +} + +void +decoder_timestamp(gcc_unused struct decoder *decoder, + gcc_unused double t) +{ +} + +DecoderCommand +decoder_data(gcc_unused struct decoder *decoder, + gcc_unused struct input_stream *is, + const void *data, size_t datalen, + gcc_unused uint16_t kbit_rate) +{ + gcc_unused ssize_t nbytes = write(1, data, datalen); + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(gcc_unused struct decoder *decoder, + gcc_unused struct input_stream *is, + gcc_unused Tag &&tag) +{ + return DecoderCommand::NONE; +} + +void +decoder_replay_gain(gcc_unused struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + const struct replay_gain_tuple *tuple = + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); +} + +void +decoder_mixramp(gcc_unused struct decoder *decoder, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + +int main(int argc, char **argv) +{ + const char *decoder_name; + struct decoder decoder; + + if (argc != 3) { + g_printerr("Usage: run_decoder DECODER URI >OUT\n"); + return 1; + } + + decoder_name = argv[1]; + decoder.uri = argv[2]; + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + g_log_set_default_handler(my_log_func, NULL); + + io_thread_init(); + io_thread_start(); + + Error error; + if (!input_stream_global_init(error)) { + LogError(error); + return 2; + } + + decoder_plugin_init_all(); + + decoder.plugin = decoder_plugin_from_name(decoder_name); + if (decoder.plugin == NULL) { + g_printerr("No such decoder: %s\n", decoder_name); + return 1; + } + + decoder.initialized = false; + + if (decoder.plugin->file_decode != NULL) { + decoder_plugin_file_decode(decoder.plugin, &decoder, + decoder.uri); + } else if (decoder.plugin->stream_decode != NULL) { + Mutex mutex; + Cond cond; + + input_stream *is = + input_stream::Open(decoder.uri, mutex, cond, error); + if (is == NULL) { + if (error.IsDefined()) + LogError(error); + else + g_printerr("input_stream::Open() failed\n"); + + return 1; + } + + decoder_plugin_stream_decode(decoder.plugin, &decoder, is); + + is->Close(); + } else { + g_printerr("Decoder plugin is not usable\n"); + return 1; + } + + decoder_plugin_deinit_all(); + input_stream_global_finish(); + io_thread_deinit(); + + if (!decoder.initialized) { + g_printerr("Decoding failed\n"); + return 1; + } + + return 0; +} diff --git a/test/run_encoder.c b/test/run_encoder.c deleted file mode 100644 index db4d3af9b..000000000 --- a/test/run_encoder.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_list.h" -#include "encoder_plugin.h" -#include "audio_format.h" -#include "audio_parser.h" -#include "conf.h" -#include "stdbin.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> - -static void -encoder_to_stdout(struct encoder *encoder) -{ - size_t length; - static char buffer[32768]; - - while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); - } -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format audio_format; - bool ret; - const char *encoder_name; - const struct encoder_plugin *plugin; - struct encoder *encoder; - struct config_param *param; - static char buffer[32768]; - ssize_t nbytes; - - /* parse command line */ - - if (argc > 3) { - g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n"); - return 1; - } - - if (argc > 1) - encoder_name = argv[1]; - else - encoder_name = "vorbis"; - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - - /* create the encoder */ - - plugin = encoder_plugin_get(encoder_name); - if (plugin == NULL) { - g_printerr("No such encoder: %s\n", encoder_name); - return 1; - } - - param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); - - encoder = encoder_init(plugin, param, &error); - if (encoder == NULL) { - g_printerr("Failed to initialize encoder: %s\n", - error->message); - g_error_free(error); - return 1; - } - - /* open the encoder */ - - if (argc > 2) { - ret = audio_format_parse(&audio_format, argv[2], - false, &error); - if (!ret) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - g_error_free(error); - return 1; - } - } - - if (!encoder_open(encoder, &audio_format, &error)) { - g_printerr("Failed to open encoder: %s\n", - error->message); - g_error_free(error); - return 1; - } - - encoder_to_stdout(encoder); - - /* do it */ - - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - ret = encoder_write(encoder, buffer, nbytes, &error); - if (!ret) { - g_printerr("encoder_write() failed: %s\n", - error->message); - g_error_free(error); - return 1; - } - - encoder_to_stdout(encoder); - } - - ret = encoder_end(encoder, &error); - if (!ret) { - g_printerr("encoder_flush() failed: %s\n", - error->message); - g_error_free(error); - return 1; - } - - encoder_to_stdout(encoder); -} diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx new file mode 100644 index 000000000..838ee708e --- /dev/null +++ b/test/run_encoder.cxx @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "EncoderList.hxx" +#include "EncoderPlugin.hxx" +#include "AudioFormat.hxx" +#include "AudioParser.hxx" +#include "ConfigData.hxx" +#include "util/Error.hxx" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> + +static void +encoder_to_stdout(Encoder &encoder) +{ + size_t length; + static char buffer[32768]; + + while ((length = encoder_read(&encoder, buffer, sizeof(buffer))) > 0) { + gcc_unused ssize_t ignored = write(1, buffer, length); + } +} + +int main(int argc, char **argv) +{ + const char *encoder_name; + static char buffer[32768]; + + /* parse command line */ + + if (argc > 3) { + g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n"); + return 1; + } + + if (argc > 1) + encoder_name = argv[1]; + else + encoder_name = "vorbis"; + + /* create the encoder */ + + const auto plugin = encoder_plugin_get(encoder_name); + if (plugin == NULL) { + g_printerr("No such encoder: %s\n", encoder_name); + return 1; + } + + config_param param; + param.AddBlockParam("quality", "5.0", -1); + + Error error; + const auto encoder = encoder_init(*plugin, param, error); + if (encoder == NULL) { + g_printerr("Failed to initialize encoder: %s\n", + error.GetMessage()); + return 1; + } + + /* open the encoder */ + + AudioFormat audio_format(44100, SampleFormat::S16, 2); + if (argc > 2) { + if (!audio_format_parse(audio_format, argv[2], false, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + } + + if (!encoder_open(encoder, audio_format, error)) { + g_printerr("Failed to open encoder: %s\n", + error.GetMessage()); + return 1; + } + + encoder_to_stdout(*encoder); + + /* do it */ + + ssize_t nbytes; + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + if (!encoder_write(encoder, buffer, nbytes, error)) { + g_printerr("encoder_write() failed: %s\n", + error.GetMessage()); + return 1; + } + + encoder_to_stdout(*encoder); + } + + if (!encoder_end(encoder, error)) { + g_printerr("encoder_flush() failed: %s\n", + error.GetMessage()); + return 1; + } + + encoder_to_stdout(*encoder); +} diff --git a/test/run_filter.c b/test/run_filter.c deleted file mode 100644 index 8e793b768..000000000 --- a/test/run_filter.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "conf.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "filter_plugin.h" -#include "pcm_volume.h" -#include "idle.h" -#include "mixer_control.h" -#include "playlist.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> - -struct playlist g_playlist; - -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -bool -mixer_set_volume(G_GNUC_UNUSED struct mixer *mixer, - G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r) -{ - return true; -} - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -static const struct config_param * -find_named_config_block(const char *block, const char *name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(block, param)) != NULL) { - const char *current_name = - config_get_block_string(param, "name", NULL); - if (current_name != NULL && strcmp(current_name, name) == 0) - return param; - } - - return NULL; -} - -static struct filter * -load_filter(const char *name) -{ - const struct config_param *param; - struct filter *filter; - GError *error = NULL; - - param = find_named_config_block("filter", name); - if (param == NULL) { - g_printerr("No such configured filter: %s\n", name); - return false; - } - - filter = filter_configured_new(param, &error); - if (filter == NULL) { - g_printerr("Failed to load filter: %s\n", error->message); - g_error_free(error); - return NULL; - } - - return filter; -} - -int main(int argc, char **argv) -{ - struct audio_format audio_format; - struct audio_format_string af_string; - bool success; - GError *error = NULL; - struct filter *filter; - const struct audio_format *out_audio_format; - char buffer[4096]; - - if (argc < 3 || argc > 4) { - g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n"); - return 1; - } - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* read configuration file (mpd.conf) */ - - config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { - g_printerr("%s:", error->message); - g_error_free(error); - return 1; - } - - /* parse the audio format */ - - if (argc > 3) { - success = audio_format_parse(&audio_format, argv[3], - false, &error); - if (!success) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - g_error_free(error); - return 1; - } - } - - /* initialize the filter */ - - filter = load_filter(argv[2]); - if (filter == NULL) - return 1; - - /* open the filter */ - - out_audio_format = filter_open(filter, &audio_format, &error); - if (out_audio_format == NULL) { - g_printerr("Failed to open filter: %s\n", error->message); - g_error_free(error); - filter_free(filter); - return 1; - } - - g_printerr("audio_format=%s\n", - audio_format_to_string(out_audio_format, &af_string)); - - /* play */ - - while (true) { - ssize_t nbytes; - size_t length; - const void *dest; - - nbytes = read(0, buffer, sizeof(buffer)); - if (nbytes <= 0) - break; - - dest = filter_filter(filter, buffer, (size_t)nbytes, - &length, &error); - if (dest == NULL) { - g_printerr("Filter failed: %s\n", error->message); - filter_close(filter); - filter_free(filter); - return 1; - } - - nbytes = write(1, dest, length); - if (nbytes < 0) { - g_printerr("Failed to write: %s\n", g_strerror(errno)); - filter_close(filter); - filter_free(filter); - return 1; - } - } - - /* cleanup and exit */ - - filter_close(filter); - filter_free(filter); - - config_global_finish(); - - return 0; -} diff --git a/test/run_filter.cxx b/test/run_filter.cxx new file mode 100644 index 000000000..085fc256b --- /dev/null +++ b/test/run_filter.cxx @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "fs/Path.hxx" +#include "AudioParser.hxx" +#include "AudioFormat.hxx" +#include "FilterPlugin.hxx" +#include "FilterInternal.hxx" +#include "pcm/PcmVolume.hxx" +#include "MixerControl.hxx" +#include "stdbin.h" +#include "util/Error.hxx" +#include "system/FatalError.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +bool +mixer_set_volume(gcc_unused Mixer *mixer, + gcc_unused unsigned volume, gcc_unused Error &error) +{ + return true; +} + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static const struct config_param * +find_named_config_block(ConfigOption option, const char *name) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(option, param)) != NULL) { + const char *current_name = param->GetBlockValue("name"); + if (current_name != NULL && strcmp(current_name, name) == 0) + return param; + } + + return NULL; +} + +static Filter * +load_filter(const char *name) +{ + const struct config_param *param; + + param = find_named_config_block(CONF_AUDIO_FILTER, name); + if (param == NULL) { + g_printerr("No such configured filter: %s\n", name); + return nullptr; + } + + Error error; + Filter *filter = filter_configured_new(*param, error); + if (filter == NULL) { + g_printerr("Failed to load filter: %s\n", error.GetMessage()); + return NULL; + } + + return filter; +} + +int main(int argc, char **argv) +{ + struct audio_format_string af_string; + Error error2; + char buffer[4096]; + + if (argc < 3 || argc > 4) { + g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + + AudioFormat audio_format(44100, SampleFormat::S16, 2); + + /* initialize GLib */ + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + g_log_set_default_handler(my_log_func, NULL); + + /* read configuration file (mpd.conf) */ + + config_global_init(); + if (!ReadConfigFile(config_path, error2)) + FatalError(error2); + + /* parse the audio format */ + + if (argc > 3) { + Error error; + if (!audio_format_parse(audio_format, argv[3], false, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + } + + /* initialize the filter */ + + Filter *filter = load_filter(argv[2]); + if (filter == NULL) + return 1; + + /* open the filter */ + + Error error; + const AudioFormat out_audio_format = filter->Open(audio_format, error); + if (!out_audio_format.IsDefined()) { + g_printerr("Failed to open filter: %s\n", error.GetMessage()); + delete filter; + return 1; + } + + g_printerr("audio_format=%s\n", + audio_format_to_string(out_audio_format, &af_string)); + + /* play */ + + while (true) { + ssize_t nbytes; + size_t length; + const void *dest; + + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes <= 0) + break; + + dest = filter->FilterPCM(buffer, (size_t)nbytes, + &length, error); + if (dest == NULL) { + g_printerr("Filter failed: %s\n", error.GetMessage()); + filter->Close(); + delete filter; + return 1; + } + + nbytes = write(1, dest, length); + if (nbytes < 0) { + g_printerr("Failed to write: %s\n", g_strerror(errno)); + filter->Close(); + delete filter; + return 1; + } + } + + /* cleanup and exit */ + + filter->Close(); + delete filter; + + config_global_finish(); + + return 0; +} diff --git a/test/run_inotify.c b/test/run_inotify.c deleted file mode 100644 index 3e7c70dba..000000000 --- a/test/run_inotify.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "inotify_source.h" - -#include <stdbool.h> -#include <sys/inotify.h> -#include <signal.h> - -static GMainLoop *main_loop; - -static void -exit_signal_handler(G_GNUC_UNUSED int signum) -{ - g_main_loop_quit(main_loop); -} - -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 -}; - -static void -my_inotify_callback(G_GNUC_UNUSED int wd, unsigned mask, - const char *name, G_GNUC_UNUSED void *ctx) -{ - g_print("mask=0x%x name='%s'\n", mask, name); -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - const char *path; - - if (argc != 2) { - g_printerr("Usage: run_inotify PATH\n"); - return 1; - } - - path = argv[1]; - - struct mpd_inotify_source *source = - mpd_inotify_source_new(my_inotify_callback, NULL, - &error); - if (source == NULL) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - int descriptor = mpd_inotify_source_add(source, path, - IN_MASK, &error); - if (descriptor < 0) { - mpd_inotify_source_free(source); - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - main_loop = g_main_loop_new(NULL, false); - - struct sigaction sa; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sa.sa_handler = exit_signal_handler; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - - g_main_loop_run(main_loop); - g_main_loop_unref(main_loop); - - mpd_inotify_source_free(source); -} diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx new file mode 100644 index 000000000..6796480e5 --- /dev/null +++ b/test/run_inotify.cxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InotifySource.hxx" +#include "event/Loop.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <sys/inotify.h> +#include <signal.h> + +static EventLoop *event_loop; + +static void +exit_signal_handler(gcc_unused int signum) +{ + event_loop->Break(); +} + +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 +}; + +static void +my_inotify_callback(gcc_unused int wd, unsigned mask, + const char *name, gcc_unused void *ctx) +{ + g_print("mask=0x%x name='%s'\n", mask, name); +} + +int main(int argc, char **argv) +{ + const char *path; + + if (argc != 2) { + g_printerr("Usage: run_inotify PATH\n"); + return 1; + } + + path = argv[1]; + + event_loop = new EventLoop(EventLoop::Default()); + + Error error; + InotifySource *source = InotifySource::Create(*event_loop, + my_inotify_callback, + nullptr, error); + if (source == NULL) { + LogError(error); + return 2; + } + + int descriptor = source->Add(path, IN_MASK, error); + if (descriptor < 0) { + delete source; + LogError(error); + return 2; + } + + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = exit_signal_handler; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + event_loop->Run(); + + delete source; + delete event_loop; +} diff --git a/test/run_input.c b/test/run_input.c deleted file mode 100644 index 676e4e618..000000000 --- a/test/run_input.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "input_init.h" -#include "input_stream.h" -#include "tag_pool.h" -#include "tag_save.h" -#include "conf.h" -#include "stdbin.h" - -#ifdef ENABLE_ARCHIVE -#include "archive_list.h" -#endif - -#include <glib.h> - -#include <unistd.h> -#include <stdlib.h> - -static void -my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, - const gchar *message, G_GNUC_UNUSED gpointer user_data) -{ - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); -} - -static int -dump_input_stream(struct input_stream *is) -{ - GError *error = NULL; - char buffer[4096]; - size_t num_read; - ssize_t num_written; - - input_stream_lock(is); - - /* wait until the stream becomes ready */ - - input_stream_wait_ready(is); - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - /* print meta data */ - - if (is->mime != NULL) - g_printerr("MIME type: %s\n", is->mime); - - /* read data and tags from the stream */ - - while (!input_stream_eof(is)) { - struct tag *tag = input_stream_tag(is); - if (tag != NULL) { - g_printerr("Received a tag:\n"); - tag_save(stderr, tag); - tag_free(tag); - } - - num_read = input_stream_read(is, buffer, sizeof(buffer), - &error); - if (num_read == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - break; - } - - num_written = write(1, buffer, num_read); - if (num_written <= 0) - break; - } - - if (!input_stream_check(is, &error)) { - g_warning("%s", error->message); - g_error_free(error); - input_stream_unlock(is); - return EXIT_FAILURE; - } - - input_stream_unlock(is); - - return 0; -} - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct input_stream *is; - int ret; - - if (argc != 2) { - g_printerr("Usage: run_input URI\n"); - return 1; - } - - /* initialize GLib */ - - g_thread_init(NULL); - g_log_set_default_handler(my_log_func, NULL); - - /* initialize MPD */ - - tag_pool_init(); - config_global_init(); - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - -#ifdef ENABLE_ARCHIVE - archive_plugin_init_all(); -#endif - - if (!input_stream_global_init(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return 2; - } - - /* open the stream and dump it */ - - GMutex *mutex = g_mutex_new(); - GCond *cond = g_cond_new(); - - is = input_stream_open(argv[1], mutex, cond, &error); - if (is != NULL) { - ret = dump_input_stream(is); - input_stream_close(is); - } else { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } else - g_printerr("input_stream_open() failed\n"); - ret = 2; - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - input_stream_global_finish(); - -#ifdef ENABLE_ARCHIVE - archive_plugin_deinit_all(); -#endif - - io_thread_deinit(); - - config_global_finish(); - tag_pool_deinit(); - - return ret; -} diff --git a/test/run_input.cxx b/test/run_input.cxx new file mode 100644 index 000000000..d278676ce --- /dev/null +++ b/test/run_input.cxx @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "TagSave.hxx" +#include "stdbin.h" +#include "tag/Tag.hxx" +#include "ConfigGlobal.hxx" +#include "InputStream.hxx" +#include "InputInit.hxx" +#include "IOThread.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#ifdef ENABLE_ARCHIVE +#include "ArchiveList.hxx" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +static int +dump_input_stream(struct input_stream *is) +{ + Error error; + char buffer[4096]; + size_t num_read; + ssize_t num_written; + + is->Lock(); + + /* wait until the stream becomes ready */ + + is->WaitReady(); + + if (!is->Check(error)) { + LogError(error); + is->Unlock(); + return EXIT_FAILURE; + } + + /* print meta data */ + + if (!is->mime.empty()) + g_printerr("MIME type: %s\n", is->mime.c_str()); + + /* read data and tags from the stream */ + + while (!is->IsEOF()) { + Tag *tag = is->ReadTag(); + if (tag != NULL) { + g_printerr("Received a tag:\n"); + tag_save(stderr, *tag); + delete tag; + } + + num_read = is->Read(buffer, sizeof(buffer), error); + if (num_read == 0) { + if (error.IsDefined()) + LogError(error); + + break; + } + + num_written = write(1, buffer, num_read); + if (num_written <= 0) + break; + } + + if (!is->Check(error)) { + LogError(error); + is->Unlock(); + return EXIT_FAILURE; + } + + is->Unlock(); + + return 0; +} + +int main(int argc, char **argv) +{ + Error error; + struct input_stream *is; + int ret; + + if (argc != 2) { + g_printerr("Usage: run_input URI\n"); + return 1; + } + + /* initialize GLib */ + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + io_thread_init(); + io_thread_start(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + if (!input_stream_global_init(error)) { + LogError(error); + return 2; + } + + /* open the stream and dump it */ + + Mutex mutex; + Cond cond; + + is = input_stream::Open(argv[1], mutex, cond, error); + if (is != NULL) { + ret = dump_input_stream(is); + is->Close(); + } else { + if (error.IsDefined()) + LogError(error); + else + g_printerr("input_stream::Open() failed\n"); + ret = 2; + } + + /* deinitialize everything */ + + input_stream_global_finish(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + + io_thread_deinit(); + + config_global_finish(); + + return ret; +} diff --git a/test/run_normalize.c b/test/run_normalize.c deleted file mode 100644 index fc26829ed..000000000 --- a/test/run_normalize.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This program is a command line interface to MPD's normalize library - * (based on AudioCompress). - * - */ - -#include "config.h" -#include "AudioCompress/compress.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "stdbin.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> -#include <string.h> - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format audio_format; - bool ret; - struct Compressor *compressor; - static char buffer[4096]; - ssize_t nbytes; - - if (argc > 2) { - g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n"); - return 1; - } - - if (argc > 1) { - ret = audio_format_parse(&audio_format, argv[1], - false, &error); - if (!ret) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - } else - audio_format_init(&audio_format, 48000, 16, 2); - - compressor = Compressor_new(0); - - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - Compressor_Process_int16(compressor, - (int16_t *)buffer, nbytes / 2); - - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes); - } - - Compressor_delete(compressor); -} diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx new file mode 100644 index 000000000..3193fefd2 --- /dev/null +++ b/test/run_normalize.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This program is a command line interface to MPD's normalize library + * (based on AudioCompress). + * + */ + +#include "config.h" +#include "AudioCompress/compress.h" +#include "AudioParser.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> +#include <string.h> + +int main(int argc, char **argv) +{ + struct Compressor *compressor; + static char buffer[4096]; + ssize_t nbytes; + + if (argc > 2) { + g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n"); + return 1; + } + + AudioFormat audio_format(48000, SampleFormat::S16, 2); + if (argc > 1) { + Error error; + if (!audio_format_parse(audio_format, argv[1], false, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + } + + compressor = Compressor_new(0); + + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + Compressor_Process_int16(compressor, + (int16_t *)buffer, nbytes / 2); + + gcc_unused ssize_t ignored = write(1, buffer, nbytes); + } + + Compressor_delete(compressor); +} diff --git a/test/run_output.c b/test/run_output.c deleted file mode 100644 index bbb1be7d2..000000000 --- a/test/run_output.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "io_thread.h" -#include "output_plugin.h" -#include "output_internal.h" -#include "output_control.h" -#include "conf.h" -#include "audio_parser.h" -#include "filter_registry.h" -#include "pcm_convert.h" -#include "event_pipe.h" -#include "idle.h" -#include "playlist.h" -#include "player_control.h" -#include "stdbin.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <unistd.h> -#include <stdlib.h> - -struct playlist g_playlist; - -void -idle_add(G_GNUC_UNUSED unsigned flags) -{ -} - -void -event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) -{ -} - -void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state) -{ -} - -void pcm_convert_deinit(G_GNUC_UNUSED struct pcm_convert_state *state) -{ -} - -const void * -pcm_convert(G_GNUC_UNUSED struct pcm_convert_state *state, - G_GNUC_UNUSED const struct audio_format *src_format, - G_GNUC_UNUSED const void *src, G_GNUC_UNUSED size_t src_size, - G_GNUC_UNUSED const struct audio_format *dest_format, - G_GNUC_UNUSED size_t *dest_size_r, - GError **error_r) -{ - g_set_error(error_r, pcm_convert_quark(), 0, - "Not implemented"); - return NULL; -} - -const struct filter_plugin * -filter_plugin_by_name(G_GNUC_UNUSED const char *name) -{ - assert(false); - return NULL; -} - -static const struct config_param * -find_named_config_block(const char *block, const char *name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(block, param)) != NULL) { - const char *current_name = - config_get_block_string(param, "name", NULL); - if (current_name != NULL && strcmp(current_name, name) == 0) - return param; - } - - return NULL; -} - -static struct audio_output * -load_audio_output(const char *name) -{ - const struct config_param *param; - GError *error = NULL; - - param = find_named_config_block(CONF_AUDIO_OUTPUT, name); - if (param == NULL) { - g_printerr("No such configured audio output: %s\n", name); - return false; - } - - static struct player_control dummy_player_control; - - struct audio_output *ao = - audio_output_new(param, &dummy_player_control, &error); - if (ao == NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - } - - return ao; -} - -static bool -run_output(struct audio_output *ao, struct audio_format *audio_format) -{ - /* open the audio output */ - - GError *error = NULL; - if (!ao_plugin_enable(ao, &error)) { - g_printerr("Failed to enable audio output: %s\n", - error->message); - g_error_free(error); - return false; - } - - if (!ao_plugin_open(ao, audio_format, &error)) { - ao_plugin_disable(ao); - g_printerr("Failed to open audio output: %s\n", - error->message); - g_error_free(error); - return false; - } - - struct audio_format_string af_string; - g_printerr("audio_format=%s\n", - audio_format_to_string(audio_format, &af_string)); - - size_t frame_size = audio_format_frame_size(audio_format); - - /* play */ - - size_t length = 0; - char buffer[4096]; - while (true) { - if (length < sizeof(buffer)) { - ssize_t nbytes = read(0, buffer + length, - sizeof(buffer) - length); - if (nbytes <= 0) - break; - - length += (size_t)nbytes; - } - - size_t play_length = (length / frame_size) * frame_size; - if (play_length > 0) { - size_t consumed = ao_plugin_play(ao, - buffer, play_length, - &error); - if (consumed == 0) { - ao_plugin_close(ao); - ao_plugin_disable(ao); - g_printerr("Failed to play: %s\n", - error->message); - g_error_free(error); - return false; - } - - assert(consumed <= length); - assert(consumed % frame_size == 0); - - length -= consumed; - memmove(buffer, buffer + consumed, length); - } - } - - ao_plugin_close(ao); - ao_plugin_disable(ao); - return true; -} - -int main(int argc, char **argv) -{ - struct audio_format audio_format; - bool success; - GError *error = NULL; - - if (argc < 3 || argc > 4) { - g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n"); - return 1; - } - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - - g_thread_init(NULL); - - /* read configuration file (mpd.conf) */ - - config_global_init(); - success = config_read_file(argv[1], &error); - if (!success) { - g_printerr("%s:", error->message); - g_error_free(error); - return 1; - } - - io_thread_init(); - if (!io_thread_start(&error)) { - g_warning("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* initialize the audio output */ - - struct audio_output *ao = load_audio_output(argv[2]); - if (ao == NULL) - return 1; - - /* parse the audio format */ - - if (argc > 3) { - success = audio_format_parse(&audio_format, argv[3], - false, &error); - if (!success) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - g_error_free(error); - return 1; - } - } - - /* do it */ - - success = run_output(ao, &audio_format); - - /* cleanup and exit */ - - audio_output_free(ao); - - io_thread_deinit(); - - config_global_finish(); - - return success ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/test/run_output.cxx b/test/run_output.cxx new file mode 100644 index 000000000..91cffbaf8 --- /dev/null +++ b/test/run_output.cxx @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "OutputControl.hxx" +#include "OutputInternal.hxx" +#include "OutputPlugin.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "Idle.hxx" +#include "Main.hxx" +#include "event/Loop.hxx" +#include "GlobalEvents.hxx" +#include "IOThread.hxx" +#include "fs/Path.hxx" +#include "AudioParser.hxx" +#include "pcm/PcmConvert.hxx" +#include "FilterRegistry.hxx" +#include "PlayerControl.hxx" +#include "stdbin.h" +#include "util/Error.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +EventLoop *main_loop; + +void +GlobalEvents::Emit(gcc_unused Event event) +{ +} + +const struct filter_plugin * +filter_plugin_by_name(gcc_unused const char *name) +{ + assert(false); + return NULL; +} + +static const struct config_param * +find_named_config_block(ConfigOption option, const char *name) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(option, param)) != NULL) { + const char *current_name = param->GetBlockValue("name"); + if (current_name != NULL && strcmp(current_name, name) == 0) + return param; + } + + return NULL; +} + +player_control::player_control(gcc_unused unsigned _buffer_chunks, + gcc_unused unsigned _buffered_before_play) {} +player_control::~player_control() {} + +static struct audio_output * +load_audio_output(const char *name) +{ + const struct config_param *param; + + param = find_named_config_block(CONF_AUDIO_OUTPUT, name); + if (param == NULL) { + g_printerr("No such configured audio output: %s\n", name); + return nullptr; + } + + static struct player_control dummy_player_control(32, 4); + + Error error; + struct audio_output *ao = + audio_output_new(*param, &dummy_player_control, error); + if (ao == nullptr) + g_printerr("%s\n", error.GetMessage()); + + return ao; +} + +static bool +run_output(struct audio_output *ao, AudioFormat audio_format) +{ + /* open the audio output */ + + Error error; + if (!ao_plugin_enable(ao, error)) { + g_printerr("Failed to enable audio output: %s\n", + error.GetMessage()); + return false; + } + + if (!ao_plugin_open(ao, audio_format, error)) { + ao_plugin_disable(ao); + g_printerr("Failed to open audio output: %s\n", + error.GetMessage()); + return false; + } + + struct audio_format_string af_string; + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); + + size_t frame_size = audio_format.GetFrameSize(); + + /* play */ + + size_t length = 0; + char buffer[4096]; + while (true) { + if (length < sizeof(buffer)) { + ssize_t nbytes = read(0, buffer + length, + sizeof(buffer) - length); + if (nbytes <= 0) + break; + + length += (size_t)nbytes; + } + + size_t play_length = (length / frame_size) * frame_size; + if (play_length > 0) { + size_t consumed = ao_plugin_play(ao, + buffer, play_length, + error); + if (consumed == 0) { + ao_plugin_close(ao); + ao_plugin_disable(ao); + g_printerr("Failed to play: %s\n", + error.GetMessage()); + return false; + } + + assert(consumed <= length); + assert(consumed % frame_size == 0); + + length -= consumed; + memmove(buffer, buffer + consumed, length); + } + } + + ao_plugin_close(ao); + ao_plugin_disable(ao); + return true; +} + +int main(int argc, char **argv) +{ + Error error; + + if (argc < 3 || argc > 4) { + g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n"); + return 1; + } + + const Path config_path = Path::FromFS(argv[1]); + + AudioFormat audio_format(44100, SampleFormat::S16, 2); + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + /* read configuration file (mpd.conf) */ + + config_global_init(); + if (!ReadConfigFile(config_path, error)) { + g_printerr("%s\n", error.GetMessage()); + return 1; + } + + main_loop = new EventLoop(EventLoop::Default()); + + io_thread_init(); + io_thread_start(); + + /* initialize the audio output */ + + struct audio_output *ao = load_audio_output(argv[2]); + if (ao == NULL) + return 1; + + /* parse the audio format */ + + if (argc > 3) { + if (!audio_format_parse(audio_format, argv[3], false, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + } + + /* do it */ + + bool success = run_output(ao, audio_format); + + /* cleanup and exit */ + + audio_output_free(ao); + + io_thread_deinit(); + + delete main_loop; + + config_global_finish(); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/run_resolver.c b/test/run_resolver.c deleted file mode 100644 index ee0bc0172..000000000 --- a/test/run_resolver.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "resolver.h" - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netdb.h> -#endif - -#include <stdlib.h> - -int main(int argc, char **argv) -{ - if (argc != 2) { - g_printerr("Usage: run_resolver HOST\n"); - return EXIT_FAILURE; - } - - GError *error = NULL; - struct addrinfo *ai = - resolve_host_port(argv[1], 80, AI_PASSIVE, SOCK_STREAM, - &error); - if (ai == NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) { - char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen, - &error); - if (p == NULL) { - freeaddrinfo(ai); - g_printerr("%s\n", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - g_print("%s\n", p); - g_free(p); - } - - freeaddrinfo(ai); - return EXIT_SUCCESS; -} diff --git a/test/run_resolver.cxx b/test/run_resolver.cxx new file mode 100644 index 000000000..7da2fd5b2 --- /dev/null +++ b/test/run_resolver.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "system/Resolver.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <glib.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netdb.h> +#endif + +#include <stdlib.h> + +int main(int argc, char **argv) +{ + if (argc != 2) { + g_printerr("Usage: run_resolver HOST\n"); + return EXIT_FAILURE; + } + + Error error; + struct addrinfo *ai = + resolve_host_port(argv[1], 80, AI_PASSIVE, SOCK_STREAM, + error); + if (ai == NULL) { + LogError(error); + return EXIT_FAILURE; + } + + for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) { + char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen, + error); + if (p == NULL) { + freeaddrinfo(ai); + LogError(error); + return EXIT_FAILURE; + } + + g_print("%s\n", p); + g_free(p); + } + + freeaddrinfo(ai); + return EXIT_SUCCESS; +} diff --git a/test/run_tcp_connect.c b/test/run_tcp_connect.c deleted file mode 100644 index bf8d9b82f..000000000 --- a/test/run_tcp_connect.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "resolver.h" -#include "io_thread.h" -#include "tcp_connect.h" -#include "fd_util.h" - -#include <assert.h> -#include <stdlib.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netdb.h> -#endif - -static struct tcp_connect *handle; -static GMutex *mutex; -static GCond *cond; -static bool done, success; - -static void -my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - close_socket(fd); - g_print("success\n"); - - g_mutex_lock(mutex); - done = success = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("error: %s\n", error->message); - g_error_free(error); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("timeout\n"); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static void -my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx) -{ - assert(!done); - assert(!success); - - g_printerr("canceled\n"); - - g_mutex_lock(mutex); - done = true; - g_cond_signal(cond); - g_mutex_unlock(mutex); -} - -static const struct tcp_connect_handler my_tcp_connect_handler = { - .success = my_tcp_connect_success, - .error = my_tcp_connect_error, - .timeout = my_tcp_connect_timeout, - .canceled = my_tcp_connect_canceled, -}; - -int main(int argc, char **argv) -{ - if (argc != 2) { - g_printerr("Usage: run_tcp_connect IP:PORT\n"); - return 1; - } - - GError *error = NULL; - struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM, - &error); - if (ai == NULL) { - g_printerr("%s\n", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* initialize GLib */ - - g_thread_init(NULL); - - /* initialize MPD */ - - io_thread_init(); - if (!io_thread_start(&error)) { - freeaddrinfo(ai); - g_printerr("%s", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - /* open the connection */ - - mutex = g_mutex_new(); - cond = g_cond_new(); - - tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000, - &my_tcp_connect_handler, NULL, - &handle); - freeaddrinfo(ai); - - if (handle != NULL) { - g_mutex_lock(mutex); - while (!done) - g_cond_wait(cond, mutex); - g_mutex_unlock(mutex); - - tcp_connect_free(handle); - } - - g_cond_free(cond); - g_mutex_free(mutex); - - /* deinitialize everything */ - - io_thread_deinit(); - - return EXIT_SUCCESS; -} diff --git a/test/signals.c b/test/signals.c deleted file mode 100644 index 15761f6b0..000000000 --- a/test/signals.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "signals.h" -#ifndef WIN32 - -#include "mpd_error.h" - -#include <glib.h> - -#include <signal.h> -#include <errno.h> -#include <string.h> - -static void -quit_signal_handler(G_GNUC_UNUSED int signum) -{ - on_quit(); -} - -static void -x_sigaction(int signum, const struct sigaction *act) -{ - if (sigaction(signum, act, NULL) < 0) - MPD_ERROR("sigaction() failed: %s", g_strerror(errno)); -} - -#endif - -void -signals_init(void) -{ -#ifndef WIN32 - struct sigaction sa; - - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sa.sa_handler = SIG_IGN; - x_sigaction(SIGPIPE, &sa); - - sa.sa_handler = quit_signal_handler; - x_sigaction(SIGINT, &sa); - x_sigaction(SIGTERM, &sa); -#endif -} diff --git a/test/signals.h b/test/signals.h deleted file mode 100644 index e524d35e2..000000000 --- a/test/signals.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIGNALS_H -#define MPD_SIGNALS_H - -void -on_quit(void); - -void -signals_init(void); - -#endif diff --git a/test/software_volume.c b/test/software_volume.c deleted file mode 100644 index 2357da672..000000000 --- a/test/software_volume.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * This program is a command line interface to MPD's software volume - * library (pcm_volume.c). - * - */ - -#include "config.h" -#include "pcm_volume.h" -#include "audio_parser.h" -#include "audio_format.h" -#include "stdbin.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> - -int main(int argc, char **argv) -{ - GError *error = NULL; - struct audio_format audio_format; - bool ret; - static char buffer[4096]; - ssize_t nbytes; - - if (argc > 2) { - g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n"); - return 1; - } - - if (argc > 1) { - ret = audio_format_parse(&audio_format, argv[1], - false, &error); - if (!ret) { - g_printerr("Failed to parse audio format: %s\n", - error->message); - return 1; - } - } else - audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); - - while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - if (!pcm_volume(buffer, nbytes, audio_format.format, - PCM_VOLUME_1 / 2)) { - g_printerr("pcm_volume() has failed\n"); - return 2; - } - - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes); - } -} diff --git a/test/software_volume.cxx b/test/software_volume.cxx new file mode 100644 index 000000000..19a0be88c --- /dev/null +++ b/test/software_volume.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This program is a command line interface to MPD's software volume + * library (pcm_volume.c). + * + */ + +#include "config.h" +#include "pcm/PcmVolume.hxx" +#include "AudioParser.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + static char buffer[4096]; + ssize_t nbytes; + + if (argc > 2) { + g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n"); + return 1; + } + + Error error; + AudioFormat audio_format(48000, SampleFormat::S16, 2); + if (argc > 1) { + if (!audio_format_parse(audio_format, argv[1], false, error)) { + g_printerr("Failed to parse audio format: %s\n", + error.GetMessage()); + return 1; + } + } + + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + if (!pcm_volume(buffer, nbytes, + audio_format.format, + PCM_VOLUME_1 / 2)) { + g_printerr("pcm_volume() has failed\n"); + return 2; + } + + gcc_unused ssize_t ignored = write(1, buffer, nbytes); + } +} diff --git a/test/test_pcm_all.h b/test/test_pcm_all.h deleted file mode 100644 index 493619b55..000000000 --- a/test/test_pcm_all.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEST_PCM_ALL_H -#define MPD_TEST_PCM_ALL_H - -void -test_pcm_dither_24(void); - -void -test_pcm_dither_32(void); - -void -test_pcm_pack_24(void); - -void -test_pcm_unpack_24(void); - -void -test_pcm_channels_16(void); - -void -test_pcm_channels_32(void); - -void -test_pcm_volume_8(void); - -void -test_pcm_volume_16(void); - -void -test_pcm_volume_24(void); - -void -test_pcm_volume_32(void); - -void -test_pcm_volume_float(void); - -#endif diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx new file mode 100644 index 000000000..18202454b --- /dev/null +++ b/test/test_pcm_all.hxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEST_PCM_ALL_HXX +#define MPD_TEST_PCM_ALL_HXX + +void +test_pcm_dither_24(); + +void +test_pcm_dither_32(); + +void +test_pcm_pack_24(); + +void +test_pcm_unpack_24(); + +void +test_pcm_channels_16(); + +void +test_pcm_channels_32(); + +void +test_pcm_volume_8(); + +void +test_pcm_volume_16(); + +void +test_pcm_volume_24(); + +void +test_pcm_volume_32(); + +void +test_pcm_volume_float(); + +void +test_pcm_format_8_to_16(); + +void +test_pcm_format_16_to_24(); + +void +test_pcm_format_16_to_32(); + +void +test_pcm_format_float(); + +void +test_pcm_mix_8(); + +void +test_pcm_mix_16(); + +void +test_pcm_mix_24(); + +void +test_pcm_mix_32(); + +#endif diff --git a/test/test_pcm_channels.c b/test/test_pcm_channels.c deleted file mode 100644 index fb3ec5c3e..000000000 --- a/test/test_pcm_channels.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "test_pcm_all.h" -#include "pcm_channels.h" -#include "pcm_buffer.h" - -#include <glib.h> - -void -test_pcm_channels_16(void) -{ - enum { N = 256 }; - int16_t src[N * 2]; - - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int(); - - struct pcm_buffer buffer; - pcm_buffer_init(&buffer); - - /* stereo to mono */ - - size_t dest_size; - const int16_t *dest = - pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) / 2); - for (unsigned i = 0; i < N; ++i) - g_assert_cmpint(dest[i], ==, - (src[i * 2] + src[i * 2 + 1]) / 2); - - /* mono to stereo */ - - dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) * 2); - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i * 2], ==, src[i]); - g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); - } - - pcm_buffer_deinit(&buffer); -} - -void -test_pcm_channels_32(void) -{ - enum { N = 256 }; - int32_t src[N * 2]; - - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int(); - - struct pcm_buffer buffer; - pcm_buffer_init(&buffer); - - /* stereo to mono */ - - size_t dest_size; - const int32_t *dest = - pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) / 2); - for (unsigned i = 0; i < N; ++i) - g_assert_cmpint(dest[i], ==, - ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2); - - /* mono to stereo */ - - dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src), - &dest_size); - g_assert(dest != NULL); - g_assert_cmpint(dest_size, ==, sizeof(src) * 2); - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i * 2], ==, src[i]); - g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); - } - - pcm_buffer_deinit(&buffer); -} diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx new file mode 100644 index 000000000..6642ed3d4 --- /dev/null +++ b/test/test_pcm_channels.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "pcm/PcmChannels.hxx" +#include "pcm/PcmBuffer.hxx" + +#include <glib.h> + +void +test_pcm_channels_16() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N * 2>(); + + PcmBuffer buffer; + + /* stereo to mono */ + + size_t dest_size; + const int16_t *dest = + pcm_convert_channels_16(buffer, 1, 2, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) / 2); + for (unsigned i = 0; i < N; ++i) + g_assert_cmpint(dest[i], ==, + (src[i * 2] + src[i * 2 + 1]) / 2); + + /* mono to stereo */ + + dest = pcm_convert_channels_16(buffer, 2, 1, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) * 2); + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i * 2], ==, src[i]); + g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); + } +} + +void +test_pcm_channels_32() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N * 2>(); + + PcmBuffer buffer; + + /* stereo to mono */ + + size_t dest_size; + const int32_t *dest = + pcm_convert_channels_32(buffer, 1, 2, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) / 2); + for (unsigned i = 0; i < N; ++i) + g_assert_cmpint(dest[i], ==, + ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2); + + /* mono to stereo */ + + dest = pcm_convert_channels_32(buffer, 2, 1, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) * 2); + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i * 2], ==, src[i]); + g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); + } +} diff --git a/test/test_pcm_dither.c b/test/test_pcm_dither.c deleted file mode 100644 index 44d105207..000000000 --- a/test/test_pcm_dither.c +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" -#include "pcm_dither.h" - -#include <glib.h> - -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - -void -test_pcm_dither_24(void) -{ - struct pcm_dither dither; - - pcm_dither_24_init(&dither); - - enum { N = 256 }; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); - - int16_t dest[N]; - - pcm_dither_24_to_16(&dither, dest, src, src + N); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8); - g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8); - } -} - -void -test_pcm_dither_32(void) -{ - struct pcm_dither dither; - - pcm_dither_24_init(&dither); - - enum { N = 256 }; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int16_t dest[N]; - - pcm_dither_32_to_16(&dither, dest, src, src + N); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8); - g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8); - } -} diff --git a/test/test_pcm_dither.cxx b/test/test_pcm_dither.cxx new file mode 100644 index 000000000..5694c17f8 --- /dev/null +++ b/test/test_pcm_dither.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "pcm/PcmDither.hxx" + +#include <glib.h> + +void +test_pcm_dither_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); + + int16_t dest[N]; + PcmDither dither; + dither.Dither24To16(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8); + g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8); + } +} + +void +test_pcm_dither_32() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(); + + int16_t dest[N]; + PcmDither dither; + dither.Dither32To16(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8); + g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8); + } +} diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx new file mode 100644 index 000000000..65d744671 --- /dev/null +++ b/test/test_pcm_format.cxx @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "pcm/PcmFormat.hxx" +#include "pcm/PcmDither.hxx" +#include "pcm/PcmUtils.hxx" +#include "pcm/PcmBuffer.hxx" +#include "AudioFormat.hxx" + +#include <glib.h> + +void +test_pcm_format_8_to_16() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int8_t, N>(); + + PcmBuffer buffer; + + size_t d_size; + PcmDither dither; + auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8, + src, sizeof(src), &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i] >> 8); +} + +void +test_pcm_format_16_to_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N>(); + + PcmBuffer buffer; + + size_t d_size; + auto d = pcm_convert_to_24(buffer, SampleFormat::S16, + src, sizeof(src), &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i] >> 8); +} + +void +test_pcm_format_16_to_32() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N>(); + + PcmBuffer buffer; + + size_t d_size; + auto d = pcm_convert_to_32(buffer, SampleFormat::S16, + src, sizeof(src), &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i] >> 16); +} + +void +test_pcm_format_float() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int16_t, N>(); + + PcmBuffer buffer1, buffer2; + + size_t f_size; + auto f = pcm_convert_to_float(buffer1, SampleFormat::S16, + src, sizeof(src), &f_size); + auto f_end = pcm_end_pointer(f, f_size); + g_assert_cmpint(f_end - f, ==, N); + + for (auto i = f; i != f_end; ++i) { + g_assert(*i >= -1.); + g_assert(*i <= 1.); + } + + PcmDither dither; + + size_t d_size; + auto d = pcm_convert_to_16(buffer2, dither, + SampleFormat::FLOAT, + f, f_size, &d_size); + auto d_end = pcm_end_pointer(d, d_size); + g_assert_cmpint(d_end - d, ==, N); + + for (size_t i = 0; i < N; ++i) + g_assert_cmpint(src[i], ==, d[i]); +} diff --git a/test/test_pcm_main.c b/test/test_pcm_main.c deleted file mode 100644 index a483baaab..000000000 --- a/test/test_pcm_main.c +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" - -#include <glib.h> - -int -main(int argc, char **argv) -{ - g_test_init (&argc, &argv, NULL); - g_test_add_func("/pcm/dither/24", test_pcm_dither_24); - g_test_add_func("/pcm/dither/32", test_pcm_dither_32); - g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24); - g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24); - g_test_add_func("/pcm/channels/16", test_pcm_channels_16); - g_test_add_func("/pcm/channels/32", test_pcm_channels_32); - - g_test_add_func("/pcm/volume/8", test_pcm_volume_8); - g_test_add_func("/pcm/volume/16", test_pcm_volume_16); - g_test_add_func("/pcm/volume/24", test_pcm_volume_24); - g_test_add_func("/pcm/volume/32", test_pcm_volume_32); - g_test_add_func("/pcm/volume/float", test_pcm_volume_float); - - g_test_run(); -} diff --git a/test/test_pcm_main.cxx b/test/test_pcm_main.cxx new file mode 100644 index 000000000..a221b26af --- /dev/null +++ b/test/test_pcm_main.cxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" + +#include <glib.h> + +int +main(int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func("/pcm/dither/24", test_pcm_dither_24); + g_test_add_func("/pcm/dither/32", test_pcm_dither_32); + g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24); + g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24); + g_test_add_func("/pcm/channels/16", test_pcm_channels_16); + g_test_add_func("/pcm/channels/32", test_pcm_channels_32); + + g_test_add_func("/pcm/volume/8", test_pcm_volume_8); + g_test_add_func("/pcm/volume/16", test_pcm_volume_16); + g_test_add_func("/pcm/volume/24", test_pcm_volume_24); + g_test_add_func("/pcm/volume/32", test_pcm_volume_32); + g_test_add_func("/pcm/volume/float", test_pcm_volume_float); + + g_test_add_func("/pcm/format/8_to_16", test_pcm_format_8_to_16); + g_test_add_func("/pcm/format/16_to_24", test_pcm_format_16_to_24); + g_test_add_func("/pcm/format/16_to_32", test_pcm_format_16_to_32); + g_test_add_func("/pcm/format/float", test_pcm_format_float); + + g_test_add_func("/pcm/mix/8", test_pcm_mix_8); + g_test_add_func("/pcm/mix/16", test_pcm_mix_16); + g_test_add_func("/pcm/mix/24", test_pcm_mix_24); + g_test_add_func("/pcm/mix/32", test_pcm_mix_32); + + g_test_run(); +} diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx new file mode 100644 index 000000000..b0e89639c --- /dev/null +++ b/test/test_pcm_mix.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" +#include "pcm/PcmMix.hxx" + +#include <glib.h> + +template<typename T, SampleFormat format, typename G=GlibRandomInt<T>> +void +TestPcmMix(G g=G()) +{ + constexpr unsigned N = 256; + const auto src1 = TestDataBuffer<T, N>(g); + const auto src2 = TestDataBuffer<T, N>(g); + + /* portion1=1.0: result must be equal to src1 */ + auto result = src1; + bool success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + format, 1.0); + g_assert(success); + AssertEqualWithTolerance(result, src1, 1); + + /* portion1=0.0: result must be equal to src2 */ + result = src1; + success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + format, 0.0); + g_assert(success); + AssertEqualWithTolerance(result, src2, 1); + + /* portion1=0.5 */ + result = src1; + success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + format, 0.5); + g_assert(success); + + auto expected = src1; + for (unsigned i = 0; i < N; ++i) + expected[i] = (int64_t(src1[i]) + int64_t(src2[i])) / 2; + + AssertEqualWithTolerance(result, expected, 1); +} + +void +test_pcm_mix_8() +{ + TestPcmMix<int8_t, SampleFormat::S8>(); +} + +void +test_pcm_mix_16() +{ + TestPcmMix<int16_t, SampleFormat::S16>(); +} + +void +test_pcm_mix_24() +{ + TestPcmMix<int32_t, SampleFormat::S24_P32>(GlibRandomInt24()); +} + +void +test_pcm_mix_32() +{ + TestPcmMix<int32_t, SampleFormat::S32>(); +} diff --git a/test/test_pcm_pack.c b/test/test_pcm_pack.c deleted file mode 100644 index 5127536fb..000000000 --- a/test/test_pcm_pack.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" -#include "pcm_pack.h" - -#include <glib.h> - -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - -void -test_pcm_pack_24(void) -{ - enum { N = 256 }; - int32_t src[N * 3]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); - - uint8_t dest[N * 3]; - - pcm_pack_24(dest, src, src + N); - - for (unsigned i = 0; i < N; ++i) { - int32_t d; - if (G_BYTE_ORDER == G_BIG_ENDIAN) - d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8) - | dest[i * 3 + 2]; - else - d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8) - | dest[i * 3]; - if (d & 0x800000) - d |= 0xff000000; - - g_assert_cmpint(d, ==, src[i]); - } -} - -void -test_pcm_unpack_24(void) -{ - enum { N = 256 }; - uint8_t src[N * 3]; - for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) - src[i] = g_random_int_range(0, 256); - - int32_t dest[N]; - - pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src)); - - for (unsigned i = 0; i < N; ++i) { - int32_t s; - if (G_BYTE_ORDER == G_BIG_ENDIAN) - s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8) - | src[i * 3 + 2]; - else - s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8) - | src[i * 3]; - if (s & 0x800000) - s |= 0xff000000; - - g_assert_cmpint(s, ==, dest[i]); - } -} diff --git a/test/test_pcm_pack.cxx b/test/test_pcm_pack.cxx new file mode 100644 index 000000000..313948838 --- /dev/null +++ b/test/test_pcm_pack.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" +#include "test_pcm_util.hxx" + +extern "C" { +#include "pcm/pcm_pack.h" +} + +#include <glib.h> + +void +test_pcm_pack_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); + + uint8_t dest[N * 3]; + pcm_pack_24(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + int32_t d; + if (G_BYTE_ORDER == G_BIG_ENDIAN) + d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8) + | dest[i * 3 + 2]; + else + d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8) + | dest[i * 3]; + if (d & 0x800000) + d |= 0xff000000; + + g_assert_cmpint(d, ==, src[i]); + } +} + +void +test_pcm_unpack_24() +{ + constexpr unsigned N = 256; + const auto src = TestDataBuffer<uint8_t, N * 3>(); + + int32_t dest[N]; + pcm_unpack_24(dest, src.begin(), src.end()); + + for (unsigned i = 0; i < N; ++i) { + int32_t s; + if (G_BYTE_ORDER == G_BIG_ENDIAN) + s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8) + | src[i * 3 + 2]; + else + s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8) + | src[i * 3]; + if (s & 0x800000) + s |= 0xff000000; + + g_assert_cmpint(s, ==, dest[i]); + } +} diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx new file mode 100644 index 000000000..84ba074fd --- /dev/null +++ b/test/test_pcm_util.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 <glib.h> + +#include <array> + +#include <stddef.h> +#include <stdint.h> + +template<typename T> +struct GlibRandomInt { + T operator()() const { + return T(g_random_int()); + } +}; + +struct GlibRandomInt24 : GlibRandomInt<int32_t> { + int32_t operator()() const { + auto t = GlibRandomInt::operator()(); + t &= 0xffffff; + if (t & 0x800000) + t |= 0xff000000; + return t; + } +}; + +struct GlibRandomFloat { + float operator()() const { + return g_random_double_range(-1.0, 1.0); + } +}; + +template<typename T, size_t N> +class TestDataBuffer : std::array<T, N> { +public: + using typename std::array<T, N>::const_pointer; + using std::array<T, N>::size; + using std::array<T, N>::begin; + using std::array<T, N>::end; + using std::array<T, N>::operator[]; + + template<typename G=GlibRandomInt<T>> + TestDataBuffer(G g=G()):std::array<T, N>() { + for (auto &i : *this) + i = g(); + + } + + operator typename std::array<T, N>::const_pointer() const { + return begin(); + } +}; + +template<typename T> +bool +AssertEqualWithTolerance(const T &a, const T &b, unsigned tolerance) +{ + g_assert_cmpint(a.size(), ==, b.size()); + + for (unsigned i = 0; i < a.size(); ++i) { + int64_t x = a[i], y = b[i]; + + g_assert_cmpint(x, >=, y - int64_t(tolerance)); + g_assert_cmpint(x, <=, y + int64_t(tolerance)); + } + + return true; +} diff --git a/test/test_pcm_volume.c b/test/test_pcm_volume.c deleted file mode 100644 index 713645cf1..000000000 --- a/test/test_pcm_volume.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "test_pcm_all.h" -#include "pcm_volume.h" - -#include <glib.h> - -#include <string.h> - -void -test_pcm_volume_8(void) -{ - enum { N = 256 }; - static const int8_t zero[N]; - int8_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int8_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -void -test_pcm_volume_16(void) -{ - enum { N = 256 }; - static const int16_t zero[N]; - int16_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int16_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -/** - * Generate a random 24 bit PCM sample. - */ -static int32_t -random24(void) -{ - int32_t x = g_random_int() & 0xffffff; - if (x & 0x800000) - x |= 0xff000000; - return x; -} - -void -test_pcm_volume_24(void) -{ - enum { N = 256 }; - static const int32_t zero[N]; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = random24(); - - int32_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -void -test_pcm_volume_32(void) -{ - enum { N = 256 }; - static const int32_t zero[N]; - int32_t src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_int(); - - int32_t dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) { - g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); - g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); - } -} - -void -test_pcm_volume_float(void) -{ - enum { N = 256 }; - static const float zero[N]; - float src[N]; - for (unsigned i = 0; i < N; ++i) - src[i] = g_random_double_range(-1.0, 1.0); - - float dest[N]; - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, - 0), ==, true); - g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, - PCM_VOLUME_1), ==, true); - g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); - - memcpy(dest, src, sizeof(src)); - g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT, - PCM_VOLUME_1 / 2), ==, true); - - for (unsigned i = 0; i < N; ++i) - g_assert_cmpfloat(dest[i], ==, src[i] / 2); -} diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx new file mode 100644 index 000000000..d5aa3782e --- /dev/null +++ b/test/test_pcm_volume.cxx @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test_pcm_all.hxx" +#include "pcm/PcmVolume.hxx" +#include "test_pcm_util.hxx" + +#include <glib.h> + +#include <algorithm> + +#include <string.h> + +void +test_pcm_volume_8() +{ + constexpr unsigned N = 256; + static int8_t zero[N]; + const auto src = TestDataBuffer<int8_t, N>(); + + int8_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_16() +{ + constexpr unsigned N = 256; + static int16_t zero[N]; + const auto src = TestDataBuffer<int16_t, N>(); + + int16_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_24() +{ + constexpr unsigned N = 256; + static int32_t zero[N]; + const auto src = TestDataBuffer<int32_t, N>(GlibRandomInt24()); + + int32_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_32() +{ + constexpr unsigned N = 256; + static int32_t zero[N]; + const auto src = TestDataBuffer<int32_t, N>(); + + int32_t dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] - 1) / 2); + g_assert_cmpint(dest[i], <=, src[i] / 2 + 1); + } +} + +void +test_pcm_volume_float() +{ + constexpr unsigned N = 256; + static float zero[N]; + const auto src = TestDataBuffer<float, N>(GlibRandomFloat()); + + float dest[N]; + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT, + 0), ==, true); + g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT, + PCM_VOLUME_1), ==, true); + g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0); + + std::copy(src.begin(), src.end(), dest); + g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT, + PCM_VOLUME_1 / 2), ==, true); + + for (unsigned i = 0; i < N; ++i) + g_assert_cmpfloat(dest[i], ==, src[i] / 2); +} diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c deleted file mode 100644 index 5543edbba..000000000 --- a/test/test_queue_priority.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "queue.h" -#include "song.h" - -void -song_free(G_GNUC_UNUSED struct song *song) -{ -} - -G_GNUC_UNUSED -static void -dump_order(const struct queue *queue) -{ - g_printerr("queue length=%u, order:\n", queue_length(queue)); - for (unsigned i = 0; i < queue_length(queue); ++i) - g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], - queue->items[queue->order[i]].priority); -} - -static void -check_descending_priority(G_GNUC_UNUSED const struct queue *queue, - unsigned start_order) -{ - assert(start_order < queue_length(queue)); - - uint8_t last_priority = 0xff; - for (unsigned order = start_order; order < queue_length(queue); ++order) { - unsigned position = queue_order_to_position(queue, order); - uint8_t priority = queue->items[position].priority; - assert(priority <= last_priority); - (void)last_priority; - last_priority = priority; - } -} - -int -main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) -{ - struct song songs[16]; - - struct queue queue; - queue_init(&queue, 32); - - for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) - queue_append(&queue, &songs[i], 0); - - assert(queue_length(&queue) == G_N_ELEMENTS(songs)); - - /* priority=10 for 4 items */ - - queue_set_priority_range(&queue, 4, 8, 10, -1); - - queue.random = true; - queue_shuffle_order(&queue); - check_descending_priority(&queue, 0); - - for (unsigned i = 0; i < 4; ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) < 4); - } - - for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - /* priority=50 one more item */ - - queue_set_priority_range(&queue, 15, 16, 50, -1); - check_descending_priority(&queue, 0); - - assert(queue_position_to_order(&queue, 15) == 0); - - for (unsigned i = 0; i < 4; ++i) { - assert(queue_position_to_order(&queue, i) >= 4); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) >= 1 && - queue_position_to_order(&queue, i) < 5); - } - - for (unsigned i = 8; i < 15; ++i) { - assert(queue_position_to_order(&queue, i) >= 5); - } - - /* priority=20 for one of the 4 priority=10 items */ - - queue_set_priority_range(&queue, 3, 4, 20, -1); - check_descending_priority(&queue, 0); - - assert(queue_position_to_order(&queue, 3) == 1); - assert(queue_position_to_order(&queue, 15) == 0); - - for (unsigned i = 0; i < 3; ++i) { - assert(queue_position_to_order(&queue, i) >= 5); - } - - for (unsigned i = 4; i < 8; ++i) { - assert(queue_position_to_order(&queue, i) >= 2 && - queue_position_to_order(&queue, i) < 6); - } - - for (unsigned i = 8; i < 15; ++i) { - assert(queue_position_to_order(&queue, i) >= 6); - } - - /* priority=20 for another one of the 4 priority=10 items; - pass "after_order" (with priority=10) and see if it's moved - after that one */ - - unsigned current_order = 4; - unsigned current_position = - queue_order_to_position(&queue, current_order); - - unsigned a_order = 3; - unsigned a_position = queue_order_to_position(&queue, a_order); - assert(queue.items[a_position].priority == 10); - queue_set_priority(&queue, a_position, 20, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 4); - - check_descending_priority(&queue, current_order + 1); - - /* priority=70 for one of the last items; must be inserted - right after the current song, before the priority=20 one we - just created */ - - unsigned b_order = 10; - unsigned b_position = queue_order_to_position(&queue, b_order); - assert(queue.items[b_position].priority == 0); - queue_set_priority(&queue, b_position, 70, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - b_order = queue_position_to_order(&queue, b_position); - assert(b_order == 4); - - check_descending_priority(&queue, current_order + 1); - - /* priority=60 for the old prio50 item; must not be moved, - because it's before the current song, and it's status - hasn't changed (it was already higher before) */ - - unsigned c_order = 0; - unsigned c_position = queue_order_to_position(&queue, c_order); - assert(queue.items[c_position].priority == 50); - queue_set_priority(&queue, c_position, 60, current_order); - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - c_order = queue_position_to_order(&queue, c_position); - assert(c_order == 0); - - /* move the prio=20 item back */ - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 5); - assert(queue.items[a_position].priority == 20); - queue_set_priority(&queue, a_position, 5, current_order); - - - current_order = queue_position_to_order(&queue, current_position); - assert(current_order == 3); - - a_order = queue_position_to_order(&queue, a_position); - assert(a_order == 6); -} diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx new file mode 100644 index 000000000..2e544253d --- /dev/null +++ b/test/test_queue_priority.cxx @@ -0,0 +1,188 @@ +#include "config.h" +#include "Queue.hxx" +#include "Song.hxx" +#include "Directory.hxx" + +#include <glib.h> + +Directory detached_root; + +Directory::Directory() {} +Directory::~Directory() {} + +Song * +Song::DupDetached() const +{ + return const_cast<Song *>(this); +} + +void +Song::Free() +{ +} + +gcc_unused +static void +dump_order(const struct queue *queue) +{ + g_printerr("queue length=%u, order:\n", queue->GetLength()); + for (unsigned i = 0; i < queue->GetLength(); ++i) + g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], + queue->items[queue->order[i]].priority); +} + +static void +check_descending_priority(const struct queue *queue, + unsigned start_order) +{ + assert(start_order < queue->GetLength()); + + uint8_t last_priority = 0xff; + for (unsigned order = start_order; order < queue->GetLength(); ++order) { + unsigned position = queue->OrderToPosition(order); + uint8_t priority = queue->items[position].priority; + assert(priority <= last_priority); + (void)last_priority; + last_priority = priority; + } +} + +int +main(gcc_unused int argc, gcc_unused char **argv) +{ + static Song songs[16]; + + struct queue queue(32); + + for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) + queue.Append(&songs[i], 0); + + assert(queue.GetLength() == G_N_ELEMENTS(songs)); + + /* priority=10 for 4 items */ + + queue.SetPriorityRange(4, 8, 10, -1); + + queue.random = true; + queue.ShuffleOrder(); + check_descending_priority(&queue, 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue.PositionToOrder(i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue.PositionToOrder(i) < 4); + } + + for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { + assert(queue.PositionToOrder(i) >= 4); + } + + /* priority=50 one more item */ + + queue.SetPriorityRange(15, 16, 50, -1); + check_descending_priority(&queue, 0); + + assert(queue.PositionToOrder(15) == 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue.PositionToOrder(i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue.PositionToOrder(i) >= 1 && + queue.PositionToOrder(i) < 5); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue.PositionToOrder(i) >= 5); + } + + /* priority=20 for one of the 4 priority=10 items */ + + queue.SetPriorityRange(3, 4, 20, -1); + check_descending_priority(&queue, 0); + + assert(queue.PositionToOrder(3) == 1); + assert(queue.PositionToOrder(15) == 0); + + for (unsigned i = 0; i < 3; ++i) { + assert(queue.PositionToOrder(i) >= 5); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue.PositionToOrder(i) >= 2 && + queue.PositionToOrder(i) < 6); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue.PositionToOrder(i) >= 6); + } + + /* priority=20 for another one of the 4 priority=10 items; + pass "after_order" (with priority=10) and see if it's moved + after that one */ + + unsigned current_order = 4; + unsigned current_position = + queue.OrderToPosition(current_order); + + unsigned a_order = 3; + unsigned a_position = queue.OrderToPosition(a_order); + assert(queue.items[a_position].priority == 10); + queue.SetPriority(a_position, 20, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + a_order = queue.PositionToOrder(a_position); + assert(a_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=70 for one of the last items; must be inserted + right after the current song, before the priority=20 one we + just created */ + + unsigned b_order = 10; + unsigned b_position = queue.OrderToPosition(b_order); + assert(queue.items[b_position].priority == 0); + queue.SetPriority(b_position, 70, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + b_order = queue.PositionToOrder(b_position); + assert(b_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=60 for the old prio50 item; must not be moved, + because it's before the current song, and it's status + hasn't changed (it was already higher before) */ + + unsigned c_order = 0; + unsigned c_position = queue.OrderToPosition(c_order); + assert(queue.items[c_position].priority == 50); + queue.SetPriority(c_position, 60, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + c_order = queue.PositionToOrder(c_position); + assert(c_order == 0); + + /* move the prio=20 item back */ + + a_order = queue.PositionToOrder(a_position); + assert(a_order == 5); + assert(queue.items[a_position].priority == 20); + queue.SetPriority(a_position, 5, current_order); + + current_order = queue.PositionToOrder(current_position); + assert(current_order == 3); + + a_order = queue.PositionToOrder(a_position); + assert(a_order == 6); +} diff --git a/test/test_vorbis_encoder.c b/test/test_vorbis_encoder.c deleted file mode 100644 index 619399159..000000000 --- a/test/test_vorbis_encoder.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "encoder_list.h" -#include "encoder_plugin.h" -#include "audio_format.h" -#include "conf.h" -#include "stdbin.h" -#include "tag.h" - -#include <glib.h> - -#include <stddef.h> -#include <unistd.h> - -static uint8_t zero[256]; - -static void -encoder_to_stdout(struct encoder *encoder) -{ - size_t length; - static char buffer[32768]; - - while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { - G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); - } -} - -int -main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) -{ - G_GNUC_UNUSED bool success; - - /* create the encoder */ - - const struct encoder_plugin *plugin = encoder_plugin_get("vorbis"); - assert(plugin != NULL); - - struct config_param *param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); - - struct encoder *encoder = encoder_init(plugin, param, NULL); - assert(encoder != NULL); - - /* open the encoder */ - - struct audio_format audio_format; - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - success = encoder_open(encoder, &audio_format, NULL); - assert(success); - - encoder_to_stdout(encoder); - - /* write a block of data */ - - success = encoder_write(encoder, zero, sizeof(zero), NULL); - assert(success); - - encoder_to_stdout(encoder); - - /* write a tag */ - - success = encoder_pre_tag(encoder, NULL); - assert(success); - - encoder_to_stdout(encoder); - - struct tag *tag = tag_new(); - tag_add_item(tag, TAG_ARTIST, "Foo"); - tag_add_item(tag, TAG_TITLE, "Bar"); - - success = encoder_tag(encoder, tag, NULL); - assert(success); - - tag_free(tag); - - encoder_to_stdout(encoder); - - /* write another block of data */ - - success = encoder_write(encoder, zero, sizeof(zero), NULL); - assert(success); - - /* finish */ - - success = encoder_end(encoder, NULL); - assert(success); - - encoder_to_stdout(encoder); - - encoder_close(encoder); - encoder_finish(encoder); -} diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx new file mode 100644 index 000000000..1d95f6deb --- /dev/null +++ b/test/test_vorbis_encoder.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "EncoderList.hxx" +#include "EncoderPlugin.hxx" +#include "AudioFormat.hxx" +#include "ConfigData.hxx" +#include "stdbin.h" +#include "tag/Tag.hxx" +#include "util/Error.hxx" + +#include <stddef.h> +#include <unistd.h> + +static uint8_t zero[256]; + +static void +encoder_to_stdout(Encoder &encoder) +{ + size_t length; + static char buffer[32768]; + + while ((length = encoder_read(&encoder, buffer, sizeof(buffer))) > 0) { + gcc_unused ssize_t ignored = write(1, buffer, length); + } +} + +int +main(gcc_unused int argc, gcc_unused char **argv) +{ + gcc_unused bool success; + + /* create the encoder */ + + const auto plugin = encoder_plugin_get("vorbis"); + assert(plugin != NULL); + + config_param param; + param.AddBlockParam("quality", "5.0", -1); + + const auto encoder = encoder_init(*plugin, param, IgnoreError()); + assert(encoder != NULL); + + /* open the encoder */ + + AudioFormat audio_format(44100, SampleFormat::S16, 2); + success = encoder_open(encoder, audio_format, IgnoreError()); + assert(success); + + encoder_to_stdout(*encoder); + + /* write a block of data */ + + success = encoder_write(encoder, zero, sizeof(zero), IgnoreError()); + assert(success); + + encoder_to_stdout(*encoder); + + /* write a tag */ + + success = encoder_pre_tag(encoder, IgnoreError()); + assert(success); + + encoder_to_stdout(*encoder); + + Tag tag; + tag.AddItem(TAG_ARTIST, "Foo"); + tag.AddItem(TAG_TITLE, "Bar"); + + success = encoder_tag(encoder, &tag, IgnoreError()); + assert(success); + + encoder_to_stdout(*encoder); + + /* write another block of data */ + + success = encoder_write(encoder, zero, sizeof(zero), IgnoreError()); + assert(success); + + /* finish */ + + success = encoder_end(encoder, IgnoreError()); + assert(success); + + encoder_to_stdout(*encoder); + + encoder_close(encoder); + encoder_finish(encoder); +} diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx new file mode 100644 index 000000000..6e66c4696 --- /dev/null +++ b/test/visit_archive.cxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "stdbin.h" +#include "tag/Tag.hxx" +#include "ConfigGlobal.hxx" +#include "IOThread.hxx" +#include "InputInit.hxx" +#include "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "ArchiveVisitor.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <unistd.h> +#include <stdlib.h> + +static void +my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +class MyArchiveVisitor final : public ArchiveVisitor { + public: + virtual void VisitArchiveEntry(const char *path_utf8) override { + printf("%s\n", path_utf8); + } +}; + +int +main(int argc, char **argv) +{ + Error error; + + if (argc != 3) { + fprintf(stderr, "Usage: visit_archive PLUGIN PATH\n"); + return EXIT_FAILURE; + } + + const char *plugin_name = argv[1]; + const Path path = Path::FromFS(argv[2]); + + /* initialize GLib */ + +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init(NULL); +#endif + + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + config_global_init(); + + io_thread_init(); + io_thread_start(); + + archive_plugin_init_all(); + + if (!input_stream_global_init(error)) { + fprintf(stderr, "%s", error.GetMessage()); + return 2; + } + + /* open the archive and dump it */ + + const archive_plugin *plugin = archive_plugin_from_name(plugin_name); + if (plugin == nullptr) { + fprintf(stderr, "No such plugin: %s\n", plugin_name); + return EXIT_FAILURE; + } + + int result = EXIT_SUCCESS; + + ArchiveFile *file = archive_file_open(plugin, path.c_str(), error); + if (file != nullptr) { + MyArchiveVisitor visitor; + file->Visit(visitor); + file->Close(); + } else { + fprintf(stderr, "%s", error.GetMessage()); + result = EXIT_FAILURE; + } + + /* deinitialize everything */ + + input_stream_global_finish(); + + archive_plugin_deinit_all(); + + io_thread_deinit(); + + config_global_finish(); + + return result; +} diff --git a/valgrind.suppressions b/valgrind.suppressions index 2cce34186..3871d275b 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -10,6 +10,15 @@ ... fun:g_random_int } +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + fun:g_mutex_impl_new + fun:g_mutex_get_impl + fun:g_mutex_lock + fun:g_main_context_new +} { g_main_context_dispatch @@ -98,7 +107,7 @@ } { - g_static_private_set + <insert_a_suppression_name_here> Memcheck:Leak fun:*alloc ... @@ -106,6 +115,14 @@ } { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_intern_string +} + +{ g_get_language_names Memcheck:Leak fun:*alloc @@ -309,6 +326,29 @@ } { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:strdup + ... + fun:ao_initialize +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:calloc + fun:ao_initialize +} + +{ + <insert_a_suppression_name_here> + Memcheck:Addr4 + ... + fun:WildMidi_Init +} + +{ g_quark_from_string Memcheck:Leak fun:*alloc @@ -354,54 +394,6 @@ Memcheck:Leak fun:*alloc ... - fun:g_type_init_with_debug_flags -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:g_type_register_static -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:g_type_add_interface_static -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:g_type_add_interface_check -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:g_type_interface_add_prerequisite -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_type_class_ref -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... fun:g_*_class_intern_init } @@ -442,38 +434,6 @@ Memcheck:Leak fun:*alloc ... - fun:soup_*_class_intern_init -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:soup_auth_manager_add_type -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:soup_auth_manager_class_intern_init -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... - fun:soup_auth_manager_ntlm_class_intern_init -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - ... fun:intern_header_name } @@ -509,14 +469,3 @@ fun:call_init fun:_dl_init } - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:*alloc - fun:_dl_allocate_tls - ... - obj:*/libffado.so* - fun:call_init - fun:_dl_init -} |